Rust (계속..)

Posted on Dec 27, 2023
tl;dr: 예시 코드와 메모리 소유권

Command

(설치 확인)
$ rustup -V

(컴파일)
$ rustc <main.rs>
$ rustc <main.rs> [-o out]

(패키지 관리)
$ cargo new <project name> --bin
$ cargo run

Example Codes

Hello World

fn main() {
    println!("Hello, world!");
}
  • !: 매크로 호출

Fibonacci

use std::io;

fn main() {
    println!("This is Fibonacci program~");
    println!("Please input the order number of your fibonacci number.");
    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("Please input a valid number!");

    let input: i32 = match input.trim().parse() {
        Ok(num) => num,
        Err(_) => panic!("Plase input a valid number!"),
    };

    let mut i = 2;
    let mut a = 1;
    let mut b = 1;
    let mut answer = 1;
    while i < input {
        answer = a + b;
        a = b;
        b = answer;
        i += 1;
    }

    println!("The answer is {}", answer);
}
  • a::b: 메소드 또는 모듈의 네임스페이스
  • mut: Rust는 변수가 기본적으로 불변이다. mut를 붙여야 가변 변수가 된다.
  • .func(&mut x): Rust의 메모리 소유권.
  • match: Result 를 처리할 때 사용. 뒤에 나오는 { } 블록과 이어진다. 여기서 .parse()Result 타입은 Result<F, F::Err>.

소유권

  1. 러스트의 각각의 값은 해당값의 오너(owner)라고 불리우는 변수를 갖고 있다.
  2. 한번에 딱 하나의 오너만 존재할 수 있다.
  3. 오너가 스코프 밖으로 벗어나는 때, 값은 버려진다(dropped).

규칙 1

러스트의 각각의 값은 해당값의 오너(owner)라고 불리우는 변수를 갖고 있다.

  • 단순하게 생각하면 오너란 메모리 상의 값을 가리키는 변수 이름이다. 변수를 처음 선언할 때 그 변수가 오너이다.
  • 스택, 힙 둘 다 해당한다.

규칙 2

한번에 딱 하나의 오너만 존재할 수 있다.

let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1); // error!
  • String 타입은 힙에 동적 할당된다.
  • 이 코드는 에러를 일으킨다. s1는 s2에 참조값이 ‘이동’되면 유효하지 않다고 간주된다.
let x = 5;
let y = x;

println!("x = {}, y = {}", x, y); // ok
  • 이 코드는 스택에 있는 값을 복사할 뿐이기 때문에 잘 동작한다.

규칙 3

오너가 스코프 밖으로 벗어나는 때, 값은 버려진다(dropped).

  • 오너가 이동하지 않는 한, 스코프 밖으로 나갈 수 있는 변수는 없다.

참조자와 빌림

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1); // 빌림 (borrowing)

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}
  • Line 4: 소유권을 주는 대신 엠퍼샌드(&) 기호로 참조자를 전달할 수 있다. 참조자는 값을 읽을 수 있지만 변경할 수 없다.
1
2
3
4
5
6
7
8
9
fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world"); // error!
}
  • 이 코드는 에러를 일으킨다. 참조하는 것을 변경하는 것은 허용되지 않는다.
1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::from("hello");

    change(&mut s); // 가변 참조자
}

fn change(some_string: &mut String) {
    some_string.push_str(", world"); // ok
}
  • 이 코드는 잘 동작한다.
  • Line 4: 가변 참조자(&mut x)를 이용해서 소유권 없이 값을 변경할 수 있는 참조자를 전달할 수 있다.
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s; // not allowed
  • 주의할 점이 있다. 러스트는 어떤 스코프 내에서 같은 데이터에 대해 하나의 가변 참조자만 만들 수 있다. 이 코드는 에러를 일으킨다.
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s // error! Dangling referer
}
  • 댕글링 참조자: 포인터가 가리키는 메모리가 해제되고 없음
  • 이 코드는 에러를 일으킨다. 러스트 컴파일러는 댕글링 참조자가 발생하지 않는 것을 보장한다.

참고

Rust tutorial - Korean