RUST

[RUST] 구조체(structure)

ssh9308 2023. 12. 29. 09:00
반응형

구조체(structure) 란?

 

Rust에서 구조체(structure), 일반적으로 struct라고 줄여 부르는 것은 

 

여러 데이터를 하나의 논리적 단위로 그룹화하는 데 사용되는 사용자 정의 데이터 타입이다.

 

구조체는 관련된 데이터들을 모아서 이름을 붙이고,

 

안전하게 구성하여 사용할 수 있게 해 준다.

 

이는 다른 프로그래밍 언어의 '클래스'와 비슷한 개념이지만,

 

Rust의 구조체는 상속이나 다형성 같은 객체지향 기능을 지원하지 않는다.

 

 

 

 

구조체 타입

 

Named Field Structs

 

Named field structs는 가장 일반적인 유형의 구조체로, 각 필드에 이름이 붙어 있다.

 

이를 통해 데이터에 쉽게 접근하고, 의미 있는 방식으로 데이터를 구성할 수 있다.

 

구조체는 아래와 같이 사용할 수 있다.

#[derive(Debug)]
struct User {
    name: String,
    age: u8,
    email: String
}


fn main() { 

    let user = User {
        name: String::from("jhone"),
        age: 25,
        email: String::from("korea@naver.com")
    };

    println!("{:?}",user);
}

 

구조체 선언부에서는 사용할 필드의 이름과 데이터 타입을 먼저 지정해 준다.

 

그래고 해당 구조체의 인스턴스를 사용하기 위해서는

 

필드와 원하는 값을 mapping 시켜서 인스턴스를 생성해 준다.

 

 

아래와 같은 예제도 한번 살펴보자.


#[derive(Debug)]
struct User {
    name: String,
    age: u8,
    email: String
}


fn main() { 

    let user = User {
        name: String::from("jhone"),
        age: 25,
        email: String::from("korea@naver.com")
    };

    let user2 = User {
        name: user.name,
        age: user.age,
        email: String::from("use@naver.com")
    };

    
    println!("{}",user.email); // Ok
    
    println!("{}",user2.name); // Ok

    println!("{}",user.age); // Ok

    println!("{}",user.name); // Error
}

 

 

user2라는 인스턴스를 생성해 주었고,

 

해당 필드값은 이미 생성된 user의 값을 가져와서 인스턴스화해주고 있다.

 

코드상으로는 문제없어 보이지만, 

 

user.name에 접근할 때 문제가 생긴다.

 

이유는 소유권이 이전되었기 때문이다.

 

 

처음의 user 인스턴스를 생성해 줬을 때는 아래의 그림과 같은 모양을 하고 있다.

 

 

String 데이터는 Heap에 저장되고, u8과 같은 기본데이터 타입은 Stack에 저장된다.

 

그리고 user 변수자체도 Stack 에 저장된다.

 

그럼 새로운 user2 변수가 Stack 에 저장되게 되고,

 

기존 user의 name의 소유권을 가져오게 된다.

 

user.age 같은 경우는 기본타입이므로 소유권을 가져오는 것이 아닌 값복사가 이루어져서

 

새로운 age 데이터를 Stack에 생성해 준다.

 

그래서 user2를 생성한 다음에 기존 user에 age에 접근할 때에는 문제가 없지만,

 

name에 접근할 때는 문제가 발생하는 것이다.

 

 

 

물론 위의 코드는 아래와 같이 간결하게 표현이 가능하다.

 

#[derive(Debug)]
struct User {
    name: String,
    age: u8,
    email: String
}


fn main() { 

    let user = User {
        name: String::from("jhone"),
        age: 25,
        email: String::from("korea@naver.com")
    };

    let user2 = User {
        email: String::from("use@naver.com"),
        ..user
    };

    
    println!("{}",user.email); // Ok
    
    println!("{}",user2.name); // Ok

    println!("{}",user.age); // Ok

    println!("{}",user.name); // Error
}

 

 

 

Tuple Structs

 

이 구조체는 튜플과 유사하지만, 타입에 이름이 지정되어 있어 튜플 타입과 구별된다.

 

필드에는 이름이 없고, 인덱스를 통해 접근할 수 있다.

 

#[derive(Debug)]
struct Color(i32, i32, i32);

#[derive(Debug)]
struct Point(i32, i32, i32);


fn main() { 

    let black = Color(0,0,0);
    let origin = Point(0,0,0);

    println!("{:?}",black);
    println!("{:?}",origin);

    println!("origin[0] => {}",origin.0);
    println!("black[1] => {}",black.1);
}

 

 

 

 

 

 

구조체의 메서드(Method)

 

기본 메서드

 

Rust에서 구조체의 메서드는 구조체의 인스턴스와 관련된 함수이다.

 

이 메서드들은 구조체의 인스턴스에 대한 데이터를 읽거나 수정할 수 있도록 해준다.

 

메서드는 impl 블록 내에서 구조체와 관련하여 정의된다.

 

#[derive(Debug)]
struct Triangle {
    width: u32,
    height: u32
}

impl Triangle {
    
    // 삼각형의 넓이를 구해주는 함수
    fn area(&self) -> f32 {
        (self.width as f32) * (self.height as f32) * 0.5
    }
}


fn main() { 

    let triangle = Triangle {
        width: 5,
        height: 3
    };

    let triangle_area = triangle.area();

    println!("triangle area is {}",triangle_area);

}

 

 

 

여기서 유심히 봐야 할 것은 self 표현의 다양한 방법들이다.

 

 

1) self

 

이 키워드를 사용하는 메서드는 인스턴스의 소유권을 취한다.

 

이는 메서드가 호출된 후 인스턴스를 더 이상 사용할 수 없음을 의미한다.

 

 

2) &self

 

읽기 전용 참조로, 메서드는 데이터를 읽을 수 있지만 수정할 수는 없다.

 

 

3) &mut self

 

가변 참조로, 메서드가 인스턴스의 데이터를 변경할 수 있다.

 

 

즉 위에서는 메서드를 사용할 때 인스턴스의 소유권을 가져오는 것이 아닌

 

임대해서 사용하는 것이다.

 

#[derive(Debug)]
struct Triangle {
    width: u32,
    height: u32
}

impl Triangle {
    
    
    // 아래의 두 함수는 같은 표현이다.
    fn area(&self) -> f32 {
        (self.width as f32) * (self.height as f32) * 0.5
    }
    
    fn area(self: &Triangle) -> f32 {
        (self.width as f32) * (self.height as f32) * 0.5
    }
    
}

 

&self는 self: &Self를 간단하게 나타낼 수 있도록 Rust에서 규칙을 정한 것이다.

 

만약에 메서드에 &self 가 아닌 self를 넣어보자.

 

#[derive(Debug)]
struct Triangle {
    width: u32,
    height: u32
}

impl Triangle {
    
    // 삼각형의 넓이를 구해주는 함수
    fn area(self) -> f32 {
        (self.width as f32) * (self.height as f32) * 0.5
    }
}


fn main() { 

    let triangle = Triangle {
        width: 5,
        height: 3
    };

    let triangle_area = triangle.area();

    println!("triangle area is {}",triangle_area);
    println!("{:?}",triangle);

}

 

 

메서드를 호출할 때 메서드에 소유권이 넘어갔으므로 

 

선언된 triangle 변수에 대해서 다시 접근할 수 없게 된다.

 

그래서 위와 같은 에러가 발생하게 된다.

 

 

아래는 &mut self를 사용해서 특정 인스턴스의 값을 바꿔주는 예제이다.

#[derive(Debug)]
struct Triangle {
    width: u32,
    height: u32
}

impl Triangle {
    
    // 삼각형의 넓이를 구해주는 함수
    fn area(&self) -> f32 {
        (self.width as f32) * (self.height as f32) * 0.5
    }

    fn fix_width(&mut self, width: u32) {
        self.width = width;
    }
}


fn main() { 

    let mut triangle = Triangle {
        width: 5,
        height: 3
    };

    triangle.fix_width(33);

    println!("{:?}",triangle);

}

 

 

 

 

 

연관 함수 (Associated Functions)

 

impl 블록은 메서드뿐만 아니라 연관 함수(associated functions)도 정의할 수 있다.

 

이 함수들은 특정 인스턴스에 대해 호출되지 않고, 구조체 자체에 대해 호출된다.

 

new와 같은 생성자 함수는 일반적인 연관 함수의 예이다.

 

#[derive(Debug)]
struct Triangle {
    width: u32,
    height: u32
}

impl Triangle {
    
    fn new(width: u32, height: u32) -> Triangle {
        Triangle {
            width,
            height
        }
    }

    // 삼각형의 넓이를 구해주는 함수
    fn area(&self) -> f32 {
        (self.width as f32) * (self.height as f32) * 0.5
    }

    fn fix_width(&mut self, width: u32) {
        self.width = width;
    }
}


fn main() { 

    let tri = Triangle::new(11,30);

    println!("{:?}", tri);
}

 

 

반응형