티스토리 뷰

반응형

Struct 선언 및 이용

Struct 란?

  • 속성값들을 묶어서 관리한다. JAVA 의 클래스와 유사함.
struct User { // 구조체 정의
    name: String,
    email: String,
    active: bool,
}

fn main() {
    let user = User { // 구조체 인스턴스 생성
        name: String::from("홍길동"),
        email: String::from("gildong@email.com"),
        active: true,
    };

    println!("이용자의 이름은 = {}", user.name); // 인스턴스 값 출력
}

구조체 인스턴스의 값을 변경하고 싶은 경우 인스턴스 생성시에 mut 를 붙여줘야한다.

fn main() {
    let mut user = User { // 인스턴스 생성
        name: String::from("홍길동"),
        email: String::from("gildong@email.com"),
        active: true,
    };

    user.name = String::from("길동 홍");

    println!("이용자의 이름은 = {}", user.name); // 인스턴스 값 출력
}

build_user() 같은 유틸리티 함수를 이용해서 초기값을 정의할 수 있다. build_user() 는 active 속성이 항상 true 로 생성된다.

struct User { // 구조체 정의
    name: String,
    email: String,
    active: bool,
}

fn build_user(name: String, email: String) -> User {
    User {
        name: name,
        email: email,
        active: true,
    }
}

fn build_user(name: String, email: String) -> User {
    User {
        name, // 대입하고자 하는 필드와 파라미터 이름이 같으면 생략가능.
        email,
        active: true,
    }
}

fn main() {
    let user = build_user(String::from("홍길동"), String::from("gildong@eamil.com"));

    println!("이용자의 이름은 = {}", user.name); // 인스턴스 값 출력
}

이미 있는 구조체 인스터로부터 새로운 인스턴스를 생성하는 경우, 소유권 이전이 일어난다.

아래 예제는 user1으로 부터 user2 를 생성했다. user1의 name, email 소유권은 이미 user2 로 넘어간 상태이기 때문에 user1 은 더 이상 사용할 수 없다.

fn main() {
    let user1 = build_user(String::from("홍길동"), String::from("gildong@eamil.com"));
    let user2: User = User {
        name: user1.name,
        email: user1.email,
        active: false,
    };
    
    /*
    // .. 을 이용해서 기존 인스턴스 내용을 복사해올 수 있다. 
    let user2 = User {
	    active: false,
	    ..user1 
    }; 
    */
    println!("user2.email = {}", user2.email); 
    println!("user1.email = {}", user1.email); // ERROR! user1 의 이메일은 user2로 소유권이 넘어갔다!
    
}

튜플 구조체

  • 일반적인 튜플 사용법과 동일하지만, 튜플에 이름을 붙임으로서 타입을 보다 엄격하게 관리할 수 있다.
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn tuple_struct() {
    let color = Color(255, 255, 255);
    let point = Point(0, 0, 0);
    color.0;
    color.1;
}

화면에 구조체를 예쁘게 출력하는 방법

  1. 구조체 위에 #[derive(Debug)] 를 붙이고, “{:?}” 를 이용해 구조체 인스턴스를 출력한다.
    • #[derive(Debug)]: Debug 를 위한 뭔가를 만들어줘! (이것을 Derived Trait 이라고 부른다)
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle {
        width: 20,
        height: 30,
    };

    println!("해당 사각형의 면적은 {}.", area(&rect));
    println!("사각형 = {:?}", rect); // {} = std::display, {:?} = std::debug 
}

fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.height
}
  1. db!() 를 이용한다.
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle {
        width: 20,
        height: 30,
    };

    println!("해당 사각형의 면적은 {}.", area(&rect));
    dbg!(rect);
}

fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.height
}

메소드 Method

  • fn 과 사용법이 비슷하나, Enum, Trait, Struct 안에서 정의하고 사용한다.
  • 파라미터를 소유권임대 없이 사용하게되면 (&self 가 아니라 그냥 self : Self 로 쓰면) main() 에서 rect.area() 를 호출한 이후에 rect 를 사용할 수 없게된다. (소유권이 area 로 넘어가버리기 때문)
    • 즉, 메소드 파라미터는 반드시 참조 방식으로 선언 해줘야한다.
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle { // Rectangle 구조체 안에 정의하는 Method
    fn area(&self) -> u32 { //&self 는 self: &Self 를 축약해서 쓴 표현이다. self: &Rectangle 
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle {
        width: 20,
        height: 30,
    };

    println!("이 사각형의 면적은 {}입니다.", rect.area());
}

하나의 구조체에 여러개의 impl 을 작성할 수 있다.

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

impl Rectangle {
		fn area ...
}

impl Rectangle { 
    fn strange_calc(self: &mut Self) -> u32 { // 동일하게 &mut 를 붙이면 내부 속성을 변경할 수 있다.
        self.width = 10;
        self.width * self.height
    }
}

fn main() {
    let mut rect = Rectangle { // 속성을 변경할 수 있게 mut 을 붙여줌.
        width: 20,
        height: 30,
    };

    println!("이 사각형의 면적 이상 계산은 {}입니다.", rect.strange_calc());
}

연관함수

  • impl 블록 내에서 정의된 모든 함수는 해당 impl 의 이름이 붙은 유형과 연관되어 있기 때문에 연관함수라고 부른다.
  • self 를 첫번째 매개변수로 가지고 있지 않은 연관함수를 정의할 수 있는데(따라서 메서드가 아님), 이는 작업을 수행하기 위해 해당 유형의 인스턴스가 필요하지 않기 때문이다.
  • 함수의 소유권을 이전하지 않고 파라미터를 정의한 후, 자기 자신을 반환하는 함수
  • static 생성자 개념이라고 보면 될 것 같다.
  • 관용적으로 new 로 많이 만들지만 String::from() 처럼 from() 으로 만들기도 하고 아래처럼 square와 같이 이름지을 수도 있다.
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(self: &Self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn square(size: u32) -> Self { //Self 를 Rectangle 이라고 바꿔도 문제없음.
        Self {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect = Rectangle {
        width: 20,
        height: 30,
    };

    println!("이 사각형의 면적은 {}입니다.", rect.area());
    println!("정사각형 = {:?}", Rectangle::square(20));
}

Enum

#[derive(Debug)]
enum Color {
		Red,
		Green,
		Blue,
}

fn main() {
		let red = Color::Red;
		let green = Color::Green;
		
		println!("red = {:?}", red); //red = Red
}

와 같이 다양한 방식으로 Enum 을 사용할 수 있다.

enum Message {
    StartGame,
    WinPoint { who: String },
    ChangePlayerName(String),
}

fn message() {
    let message = Message::StartGame;
    let message2 = Message::WinPoint {
        who: String::from("길동"),
    };
    let message3 = Message::ChangePlayerName(String::from("둘리"));
}

Rust에는 Option 이란 기본 내장 Enum 클래스가 정의돼 있다.

Rust 에는 NULL 개념이 없고, 이 Option 타입의 하나인 None 인지 아닌지로 값의 유무를 결정한다.

// enum Option<T> {
//     None,
//     Some(T),
// }

fn some_values() {
    let some_number = Some(3);
    let absent_number: Option<i32> = None;
    let x: i32 = 2;

    // x + some_number; // x는 정수타입이고 some_number 는 Option 타입이라 계산이 안된다.
}

Enum 타입에 특정 기능을 부여하고 싶다면 match 를 이용하면 된다.

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        } // 중괄호 뒤에는 , 를 붙이지 않아도 된다. 하지만 붙여도 잘 돌아가니 붙이는게 좋지 않을까.
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        Some(i) => Some(i + 1),
        None => None, // 요렇게도 매칭할 수 있다.
    }
}

fn main() {
    let x = Some(5);
    println!("{:?}", plus_one(x)); // Option 에는 default 출력형식이 없어서 :? 로 출력
    println!("{:?}", plus_one(None));
}
/*

Some(6)
None

*/

match 는 선언한 모든 Enum 에 대해 정의해줘야 하지만 _라는 와일드 카드를 이용해서 간편하게 작성할 수 도 있다.

enum Message {
    StartGame,
    WinPoint { who: String },
    ChangePlayerName(String),
}

/* 패턴매치 기능으로 Enum 값에 따른 처리를 하기 좋습니다. */
fn handle_message(message: &Message) {
    match message {
        Message::StartGame => println!("게임시작!"),
        Message::WinPoint { who } => println!("{}의 득점", who),
        Message::ChangePlayerName(_) => println!("플레이어 이름변경 요청"),
    }
}

fn handle_message2(message: &Message) {
    match message {
        Message::StartGame => println!("게임시작!"),
        _ => println!("아직 정의하지 못한 메세지 입니다."),
    }
}

fn main() {
    let message = Message::WinPoint{who: String::from("홍길동")};
    handle_message2(&message); // 아직 정의하지 못한 메세지 입니다.
}
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
글 보관함