Rust[작성중]

Rust 정리

site

1
2
3
4
fn main()
{
    println!("Hello, world!");
}
1
2
$ rustc main.rs
$ ./main

프로젝트 생성, 빌드 –bin 바이너리용 프로젝트

1
2
3
4
5
6
$ cargo new hello_cargo --bin
$ cd hello_cargo
$ cargo build
$ cargo run
$ cargo check
$cargo build --release
  • let : 변수생성
  • mut - mutable
  • // : 주석
  • String UTF-8 문자열 타입

read_line : 값반환 io::Result

  • Result : 열거형
  • Result의 expect 메소드
    • Err : 작동멈춤 인자메시지 출력
    • Ok 결괏값 돌려줌 : 입력받은 바이트
    • 호출안할시 경고

println

  • {} : 변경자 값이표시되는 위치

crate 사용

러스트 코드의 묶음

1
2
3
4
Filename: Cargo.toml
[dependencies]

rand = "0.3.14"

Cargo.lock : build시 생성

  • 모든 의존 패키지 버전 기록

크레이트 업데이트 cargo update

rand::thread_rng : 현재 스레드에서만 사용되는 특별한 정수생성기

cargo doc –open : 사용한 모든 의존 패키지 제공 문서들을 빌드해서 브라우저에 표시

비교

1
2
3
4
5
match guess.cmp(&secret_number) {
    Ordering::Less    => println!("Too small!"),
    Ordering::Greater => println!("Too big!"),
    Ordering::Equal   => println!("You win!"),
}

std::cmp::Ordering

  • Less
  • Greater
  • Equal

변수 타입변경가능

1
let guess: u32 = guess.trim().parse().expect("Please type a number!");
  • trim : 처음과 끝 빈칸 제거(\n..)
  • parese : 문자열을 숫자형으로 파싱 타입명시
  • u32
  • i32
  • i64
1
2
loop 
{ break; continue;}

무한루프

  • 변수선언 let
  • 상수선언 const MAX_POINTS: u32 = 100_000모든 단어 대문자

shadowing

let을 통해 덮어쓰기 가능

1
2
3
4
5
6
7
8
9
fn main() {
    let x = 5;

    let x = x + 1;

    let x = x * 2;

    println!("The value of x is: {}", x);
}

다음과같은 응용가능

1
2
let spaces = "   ";
let spaces = spaces.len();

타입

  • signed : i 8/16/32/64
  • unsigned : u 8/16/32/64
  • arch : isize usize i/usize :아키텍처에 따라다름 32bit아키텍처 -32

정수형 리터럴

  • 10진수 :98_222
  • 16진수 0xff
  • 8진수 : 0o77
  • 2진수 0b1111_0000
  • byte : b’A’

부동소수점

  • f32/64

타입명시

1
let x: f32 = 3.0
  • bool true false
  • char Unicode Scalar

tuple

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
  
    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

배열

1
2
3
4
5
6
7
fn main() {
    let a = [1, 2, 3, 4, 5];
  let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

    let first = a[0];
    let second = a[1];
}

배열길이를 넘어 접근할시 실행중 에러

함수

위치 신경안씀

1
2
3
4
5
6
7
8
9
fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

매개변수

1
2
3
4
fn another_function(x: i32, y: i32) {
    println!("The value of x is: {}", x);
    println!("The value of y is: {}", y);
}

구문/표현식

다음은 불가능 let은 구문이며 반환값이 없음 c와 차이

1
2
3
fn main() {
    let x = (let y = 6);
}

{}은 표현식

1
2
3
4
5
6
7
8
fn main() {
    let x = 5;
    let y = {
        let x = 3;
        x + 1
    };
    println!("The value of y is: {}", y);
}

{}은 4를 반환 세미콜론없음

반환값 return을 통해 일찍 반환할수있으나 암묵적으로 마지막 표현식을 반환 세미콜론x

1
2
3
fn plus_one(x: i32) -> i32 {
    x + 1
}

세미콜론 붙을시 구문은 값을 산출하지 않기에 ()처럼 비어있는 튜플로 표현 따라서 mismatched types에러가 남.

제어문

if

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
    let number = 6;
    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

반드시 bool이어야함

다음의 응용이 가능

1
2
3
4
5
6
7
8
9
10
fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

    println!("The value of number is: {}", number);
}

각 {}의 반환은 확실하게 타입이 일치해야함. 안그럼 컴파일 에러.

1
2
3
4
5
6
7
8
9
10
11
fn main() {
    let condition = true;

    let number = if condition {
        5
    } else {
        "six"
    };

    println!("The value of number is: {}", number);
}

타입이 일치하지않으므로 에러

반복문

loop

무한루프

while

1
2
3
4
5
6
7
8
9
10
11
fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number = number - 1;
    }

    println!("LIFTOFF!!!");
}
1
2
3
4
5
6
7
fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}
1
2
3
4
5
6
fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

소유권

소유권 규칙

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

s.push_str(", world!"); // push_str()은 해당 스트링 리터럴을 스트링에 붙여줍니다.

println!("{}", s); // 이 부분이 `hello, world!`를 출력할 겁니다.

s가 스코프를 벗어날때 rust는 drop 함수를 호출

move

1
2
3
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);

복사에 대해서 러스트는 s1을 더이상 유효하지않은 참조자라 판단 따라서 에러 러스트는 자동으로 깊은 복사를 하지않음.

clone

깊은 복사

1
2
3
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);

스택에 저장할수잇는 타입에 대해서 Copy 트레잇이라고 하는 annotation이 있다. Copy 트레잇이 있으면 대입 후에 예전 변수를 사용 할 수 있다. Drop 트레잇이 구현되었다면 Copy 트레잇을 어노테이션 할 수 없음. 둘은 양립 불가능. 컴파일 에러

  • u32와 같은 모든 정수형 타입들
  • true와 false값을 갖는 부울린 타입 bool
  • f64와 같은 모든 부동 소수점 타입들
  • Copy가 가능한 타입만으로 구성된 튜플들. (i32, i32)는 Copy가 되지만, (i32, String)은 안됩니다.

소유권을 함수 매개변수로 넘기면 함수를 탈출하면서 메모리가 해제된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
    let s = String::from("hello");  // s가 스코프 안으로 들어왔습니다.
    takes_ownership(s);             // s의 값이 함수 안으로 이동했습니다...
                                    // ... 그리고 이제 더이상 유효하지 않습니다.
    let x = 5;                      // x가 스코프 안으로 들어왔습니다.

    makes_copy(x);                  // x가 함수 안으로 이동했습니다만,
                                    // i32는 Copy가 되므로, x를 이후에 계속
                                    // 사용해도 됩니다.
} // 여기서 x는 스코프 밖으로 나가고, s도 그 후 나갑니다. 하지만 s는 이미 이동되었으므로,
  // 별다른 일이 발생하지 않습니다.
fn takes_ownership(some_string: String) { // some_string이 스코프 안으로 들어왔습니다.
    println!("{}", some_string);
} // 여기서 some_string이 스코프 밖으로 벗어났고 `drop`이 호출됩니다. 메모리는
  // 해제되었습니다.
fn makes_copy(some_integer: i32) { // some_integer이 스코프 안으로 들어왔습니다.
    println!("{}", some_integer);
} // 여기서 some_integer가 스코프 밖으로 벗어났습니다. 별다른 일은 발생하지 않습니다.
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
fn main() {
    let s1 = gives_ownership();         // gives_ownership은 반환값을 s1에게
                                        // 이동시킵니다.

    let s2 = String::from("hello");     // s2가 스코프 안에 들어왔습니다.

    let s3 = takes_and_gives_back(s2);  // s2는 takes_and_gives_back 안으로
                                        // 이동되었고, 이 함수가 반환값을 s3으로도
                                        // 이동시켰습니다.
} // 여기서 s3는 스코프 밖으로 벗어났으며 drop이 호출됩니다. s2는 스코프 밖으로
  // 벗어났지만 이동되었으므로 아무 일도 일어나지 않습니다. s1은 스코프 밖으로
  // 벗어나서 drop이 호출됩니다.
fn gives_ownership() -> String {             // gives_ownership 함수가 반환 값을
                                             // 호출한 쪽으로 이동시킵니다.

    let some_string = String::from("hello"); // some_string이 스코프 안에 들어왔습니다.

    some_string                              // some_string이 반환되고, 호출한 쪽의
                                             // 함수로 이동됩니다.
}
// takes_and_gives_back 함수는 String을 하나 받아서 다른 하나를 반환합니다.
fn takes_and_gives_back(a_string: String) -> String { // a_string이 스코프
                                                      // 안으로 들어왔습니다.

    a_string  // a_string은 반환되고, 호출한 쪽의 함수로 이동됩니다.
}

s1,s3은 main을 벗어나면서 drop호출 s2는 s3로 이동해서 애초에 없음.

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

    let (s2, len) = calculate_length(s1);

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

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len()함수는 문자열의 길이를 반환합니다.

    (s, length)
}

튜플을 사용해 값을 다시 돌려받을 수 있음

참조자references 빌림 borrowing

1
2
3
fn calculate_length(s: &String) -> usize {
    s.len()
}

가변 참조

1
2
3
4
5
6
7
fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

가변 참조자는 특정한 스코프 내에 가변 참조자를 딱 하나만 만들 수 있다.

다음은 불가능

1
2
3
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;

Data race 방지.

data race는 다음중에서 일어난다.

  1. 두 개 이상의 포인터가 동시에 같은 데이터에 접근한다.
  2. 그 중 적어도 하나의 포인터가 데이터를 쓴다.
  3. 데이터에 접근하는데 동기화를 하는 어떠한 메커니즘도 없다.

불변참조자를 가지고 있을때도 컴파일 에러

1
2
3
4
let mut s = String::from("hello");
let r1 = &s; // 문제 없음
let r2 = &s; // 문제 없음
let r3 = &mut s; // 큰 문제

참조자는 항상 유효해야한다. 다음은 불가

1
2
3
4
5
6
7
fn dangle() -> &String { // dangle은 String의 참조자를 반환합니다

    let s = String::from("hello"); // s는 새로운 String입니다

    &s // 우리는 String s의 참조자를 반환합니다.
} // 여기서 s는 스코프를 벗어나고 버려집니다. 이것의 메모리는 사라집니다.
  // 위험하군요!
1
2
3
4
5
6
7
8
9
10
11
fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

이러한 값은 string 내부가 바뀌었을때 저장되어있는 결괏값은 의미가 없어진다 이를 방지하기위해.

스트링 슬라이스

1
2
3
let s = String::from("hello world");
let hello = &s[0..5]; // &s[..5];
let world = &s[6..11]; // &s[6..];   &s[..]

스트링 슬라이스 타입 &str

1
2
3
4
5
6
7
8
9
10
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}
1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s);

    s.clear(); // Error!

    println!("the first word is: {}", word);
}

구조체

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
29
30
31
32
33
34
35
36
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}


//in function
let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};
///

fn build_user(email: String, username: String) -> User {
    User {
        email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}
//다음도 허용

1
2
3
4
5
let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

나머지는 user1을 재사용

튜플 구조체

1
2
3
4
5
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

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

유사 유닛 구조체(unit-like structs) 어떤 필드도 없는 구조체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct User {
    username: &str,
    email: &str,
    sign_in_count: u64,
    active: bool,
}
fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}

다음은 라이프타임을 명시하라고 에러가 난다.

메소드 정의

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
29
30
31
32
#[derive(Debug)]
struct Rectangle {
    length: u32,
    width: u32,
}
impl Rectangle {
    fn area(&self) -> u32 {
        self.length * self.width
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.length > other.length && self.width > other.width
    }
    fn square(size: u32) -> Rectangle {
        Rectangle { length: size, width: size }
    }

}
fn main() {
    let rect1 = Rectangle { length: 50, width: 30 };
    let rect2 = Rectangle { length: 40, width: 10 };
    let rect3 = Rectangle { length: 45, width: 60 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
    let sq = Rectangle::square(3);
}

러스트는 화살표 연산자가 없다. 자동 참조 및 역참조(automatic referencing and dereferencing) 를 가지고있다

enum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum IpAddrKind {
    V4,
    V6,
}
struct IpAddr {
    kind: IpAddrKind,
    address: String,
}
let home = IpAddr {
    kind: IpAddrKind::V4,
    address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
    kind: IpAddrKind::V6,
    address: String::from("::1"),
};

다음을 개선하여 이렇게 사용가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum IpAddr {
    V4(String),
    V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
//다음과 같이도 가능
enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

참고로 ip주소 저장에 관해서는 표준라이브러리가 존재

1
2
3
4
5
6
7
8
impl Message {
    fn call(&self) {
        // 메소드 내용은 여기 정의할 수 있습니다.
    }
}

let m = Message::Write(String::from("hello"));
m.call();

Option 열거형

1
2
3
4
5
6
7
8
enum Option<T> {
    Some(T),
    None,
}

let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;

Option<T> 와 T 는 다른 타입이며 둘을 섞어서 더하거나 하지못함.

match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        },
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

Option<T>응용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),
}

만약 None과 Some중에 하나가 빠질시엔 에러 _ 사용

if let

하나의 패턴만 매칭 시키고 나머지 경우는 무시

다음은 같은 행동

1
2
3
4
5
6
7
8
9
10
11
12
let mut count = 0;

match coin {
    Coin::Quarter(state) => println!("State quarter from {:?}!", state),
    _ => count += 1,
}

if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} else {
    count += 1;
}

모듈

라이브러리 크레이트 src/lib.rs 생성

1
cargo new communticator

이렇게 하니 main.rs파일이 생겼다. 따라서 라이브러리 옵션을 주기위해서

1
cargo help new 

로 보니 –bin이 default행동이고 –lib 옵션을 넣어줘야한다.

1
cargo new --lib communticator

파일에 관한규칙.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
src/lib.rs
mod client;
mod network;

src/client.rs
mod client {
    // contents of client.rs
}


src/network.rs
fn connect() {
}
mod server ;

src/network/server.rs
fn connect() {
}

pub

pub : 코드를 공개로 만듬

1
2
3
4
5
6
7
//src/lib.rs
pub mod client;

mod network;
//src/client.rs
pub fn connect() {
}

비공개 규칙

  1. 만일 어떤 아이템이 공개라면, 이는 부모 모듈의 어디에서건 접근 가능합니다.
  2. 만일 어떤 아이템이 비공개라면, 같은 파일 내에 있는 부모 모듈 및 이 부모의 자식 모듈에서만 접근 가능합니다.

use

1
2
3
4
5
6
7
8
9
10
11
12
13
pub mod a {
    pub mod series {
        pub mod of {
            pub fn nested_modules() {}
        }
    }
}

use a::series::of::nested_modules;

fn main() {
    nested_modules();
}

다음과같이 부분적으로 사용가능

1
2
3
4
5
6
7
8
9
10
11
12
13
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

use TrafficLight::{Red, Yellow};

fn main() {
    let red = Red;
    let yellow = Yellow;
    let green = TrafficLight::Green;
}

모든걸 들고올수도있음

1
2
3
4
5
6
7
8
9
10
11
12
13
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

use TrafficLight::*;

fn main() {
    let red = Red;
    let yellow = Yellow;
    let green = Green;
}

::client::connect(); super::client::connect();

1
2
3
4
5
6
7
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        client::connect();
    }
}

다음은 에러

communicator ├── client ├── network | └── client └── tests

이런구조로 되어있기에 같은 계층의 client에 접근불가. 다음의 방법으로 접근가능.

  1. ::client::connect();
  2. super::client::connect();
1
2
3
4
5
6
7
8
mod tests {
    use super::client;
    #[test]
    fn it_works() {
        client::connect();
    }
}

컬렉션

vector

1
2
3
4
5
6
7
8
9
10
11
12
13
let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];
v.push(5);
v.push(6);
v.push(7);
v.push(8);

let third: &i32 = &v[2];
let third: Option<&i32> = v.get(2);

for i in &v {
    println!("{}", i);
}
1
2
3
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);

다음은 빌림규칙에 의해 에러.

열거형을 사용하여 여러 타입을 저장 할 수 있음.

1
2
3
4
5
6
7
8
9
10
11
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

string

UTF-8

1
2
3
4
5
6
7
let mut s = String::new();

let data = "initial contents";
let s = data.to_string();
// the method also works on a literal directly:
let s = "initial contents".to_string();
let s = String::from("initial contents");

push 사용

1
2
3
4
5
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(&s2); // 문자열
println!("s2 is {}", s2);
s.push('l'); // 한글자
1
2
3
4
5
6
7
8
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1은 여기서 이동되어 더이상 쓸 수 없음을 유의하세요

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = s1 + "-" + &s2 + "-" + &s3;

러스트는 스트링 내부의 인덱싱을 지원하지 않는다.

1
2
3
4
let hello = "Здравствуйте";

let s = &hello[0..4];
Зд
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for c in "नमस्ते".chars() {
    println!("{}", c);
}
output






for b in "नमस्ते".bytes() {
    println!("{}", b);
}
output
224
164
168
224
// ... etc

hash map

생성

1
2
3
4
5
6
7
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

vec -> hashmap

1
2
3
4
5
6
7
use std::collections::HashMap;

let teams  = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];

let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();

접근

1
2
3
4
5
6
7
8
9
10
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

let team_name = String::from("Blue");
let score = scores.get(&team_name);

1
2
3
4
5
6
7
8
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

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

같은 키일땐 덮어쓴다.

키에 할당된 값이 없을때만 삽입하기

1
2
3
4
5
6
7
8
9
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

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

에러

panic! 발생시 unwinding을 함으로 스택을 거슬러 데이터를 제거 이대신 abort를 발생시킬 수 있는데. Cargo.toml의 적합한 [profile] 섹션에 panic = ‘abort’를 추가

1
panic!("crash and burn");

백트레이스 출력

1
$ RUST_BACKTRACE=1 cargo run

Result 정의

1
2
3
4
5
enum Result<T, E> {
    Ok(T),
    Err(E),
}

f의 타입과 에러처리법

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
    //type std::result::Result<std::fs::File, std::io::Error>
    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("There was a problem opening the file: {:?}", error)
        },
    };
}

특정 에러에 대해 처리

match사용

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
use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(ref error) if error.kind() == ErrorKind::NotFound => {
            match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => {
                    panic!(
                        "Tried to create file but there was a problem: {:?}",
                        e
                    )
                },
            }
        },
        Err(error) => {
            panic!(
                "There was a problem opening the file: {:?}",
                error
            )
        },
    };
}
1
2
3
4
5
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
}

unwrap Err variant시 panic!을 호출

1
2
3
4
5
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

expect Err variant시 panic!을 호출 인자로 넣은 메시지를 먼저 출력해줌

에러전파하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

물음표 연산자

1
2
3
4
5
6
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

?가 들어갈시에 에러타입일 경우 그대로 반환

다음과같이 연속적으로 사용가능

1
2
3
4
5
6
7
fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}

Result를 반환하는 함수에서만 사용될 수 있다

panic!과 Result처리

테스트에서는 모두 panic을 써도 상관없다.

컴파일러보다 더 많은 정보를 가지고 있을때 panic을 써도 된다.

에러처리를 위한 가이드

panic!을 넣는 경우

  • 벌어질것으로 예상되는 무언가가 아닌경우
  • 이후의 코드는 나쁜 상태에 있지 않아야만 하는경우
  • 뾰족한 수가 업는 경우

제네릭

1
2
3
4
5
6
7
8
9
10
11
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

해당 코드는 에러 트레잇 개념을 알아야함.

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
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

트레잇

트레잇(trait) : 러스트 컴파일러에게 특정한 타입이 갖고 다른 타입들과 함께 공유할 수도 있는 기능

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
29
30
31
32
33
34
35
36
37
38
pub trait Summarizable {
    fn summary(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summarizable for NewsArticle {
    fn summary(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summarizable for Tweet {
    fn summary(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
//main
let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
};

println!("1 new tweet: {}", tweet.summary());

만약 이 파일이 lib.rs가아닌 aggregator라고 불리는 크레이트에 대한 것이고 이를 이용해 WeatherForecast구조체에 대하여 Summarizable을 구현하려한다면 스코프로 가져와야한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extern crate aggregator;

use aggregator::Summarizable;
// 공개 trait이어야함

struct WeatherForecast {
    high_temp: f64,
    low_temp: f64,
    chance_of_precipitation: f64,
}

impl Summarizable for WeatherForecast {
    fn summary(&self) -> String {
        format!("The high will be {}, and the low will be {}. The chance of
        precipitation is {}%.", self.high_temp, self.low_temp,
        self.chance_of_precipitation)
    }
}

트레잇 혹은 타입이 우리의 크레이트 내의 것일 경우에만 해당 타입에서의 트레잇을 정의할 수 있습니다.

기본구현

1
2
3
4
5
6
7
pub trait Summarizable {
    fn summary(&self) -> String {
        String::from("(Read more...)")
    }
}
//사용
impl Summarizable for NewsArticle {}

트레잇 바운드

1
2
3
pub fn notify<T: Summarizable>(item: T) {
    println!("Breaking news! {}", item.summary());
}

trait을 포함한 largest

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::cmp::PartialOrd;

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

copy, clone 트레잇이 따로 필요하지 않은 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::cmp::PartialOrd;

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list.iter() {
        if item > largest {
            largest = &item;
        }
    }

    largest
}

lifetime

다음의 댕글링 참조자는 사용불가

1
2
3
4
5
6
7
8
9
{
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("r: {}", r);
}

borrow checker는 컴파일러가 모든 빌림이 유효한지 결정하기 위해 스코프를 비교

라이프타임 명시 `a

1
2
3
&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
1
2
3
4
5
6
7
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
1
2
3
4
5
6
7
8
9
fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("The longest string is {}", result);
    }
}
1
2
3
4
5
6
7
8
9
fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}
1
2
3
4
fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

다음은 컴파일 되지않음.

구조체

1
2
3
4
5
6
7
8
9
10
11
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.')
        .next()
        .expect("Could not find a '.'");
    let i = ImportantExcerpt { part: first_sentence };
}
  • 입력 라이프타임(input lifetime) 메소드의 파라미터에 대한 라이프타임
  • 출력 라이프타임(output lifetime) 반환 값에 대한 라이프타임

컴파일러가 라이프타임을 추론하는 규칙

  1. 참조자인 각각의 파라미터는 고유한 라이프타임 파라미터를 갖습니다. 바꿔 말하면, 하나의 파라미터를 갖는 함수는 하나의 라이프타임 파라미터를 갖고: fn foo<’a>(x: &’a i32), 두 개의 파라미터를 갖는 함수는 두 개의 라이프타임 파라미터를 따로 갖고: fn foo<’a, ‘b>(x: &’a i32, y: &’b i32), 이와 같은 식입니다.
  2. 만일 정확히 딱 하나의 라이프타임 파라미터만 있다면, 그 라이프타임이 모든 출력 라이프타임 파라미터들에 대입됩니다: fn foo<’a>(x: &’a i32) -> &’a i32.
  3. 만일 여러 개의 입력 라이프타임 파라미터가 있는데, 메소드라서 그중 하나가 &self 혹은 &mut self라고 한다면, self의 라이프타임이 모든 출력 라이프타임 파라미터에 대입됩니다. 이는 메소드의 작성을 더욱 멋지게 만들어줍니다.

메소드 정의 내에서의 라이프타임 명시

1
2
3
4
5
impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

정적 라이프타임(Static lifetime)

모든 스트링 리터럴은 `static 라이프타임을 가지고 있으며 다음과같이 명시해줄수 있음.

1
let s: &'static str = "I have a static lifetime.";
1
2
3
4
5
6
7
8
9
10
11
12
use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where T: Display
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

test

test 속성(attribute)이 주석으로 달려진 (annotated) 함수 테스트 함수는 세가지 동작을 수행

  1. 필요한 데이터 혹은 상태를 설정하기
  2. 우리가 테스트하고 싶은 코드를 실행하기
  3. 그 결과가 우리 예상대로인지 단언하기(assert)
1
cargo test
1
2
3
4
5
6
7
8
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle { length: 8, width: 7 };
        let smaller = Rectangle { length: 5, width: 1 };

        assert!(larger.can_hold(&smaller));
    }
}
  • assert!
  • assert_eq!
  • assert_ne! 밑의 두개는 비교되는 값들이 PartialEq와 Debug 트레잇을 구현해야 한다.
1
2
3
4
5
6
7
8
#[test]
fn greeting_contains_name() {
    let result = greeting("Carol");
    assert!(
        result.contains("Carol"),
        "Greeting did not contain name, value was `{}`", result
    );
}

메세지를 넣는 예시

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
pub struct Guess {
    value: u32,
}

impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess {
            value
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn greater_than_100() {
        Guess::new(200);
    }
}

패닉을 발생시켜 패닉이 일어나는지 체크 패닉이 안일어날시에 실패

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
// --snip

impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 {
            panic!("Guess value must be greater than or equal to 1, got {}.",
                   value);
        } else if value > 100 {
            panic!("Guess value must be less than or equal to 100, got {}.",
                   value);
        }

        Guess {
            value
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic(expected = "Guess value must be less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}

테스트는 기본적으로 병렬로 이루어지는데 이때 데이터간섭이 일어날 수 있다. 이를 방지하기위해서 테스트를 한번씩만 실행시키고 싶은경우 다음과같이 스레드 갯수를 제어할수있다

1
2
$ cargo test -- --test-threads=1

테스트되는 함수에서 출력문은 테스트가 성공시에는 출력되지않는다 이를 출력하게 하려면 다음의 플래그를 사용하면된다.

1
$ cargo test -- --nocapture

특정 테스트 함수 명만을 넘겨서 단일 테스트 할 수 있음.

1
$ cargo test one_hundred

테스트 이름의 일부부만 특정할 수 있고 이름이 들어가는 테스트함수가 테스트된다.

1
2
3
4
5
6
7
8
9
$ cargo test add
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running target/debug/deps/adder-06a75b4a1f2515e9

running 2 tests
test tests::add_two_and_two ... ok
test tests::add_three_and_two ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out

특정테스트 제외

1
2
3
4
5
6
7
8
9
10
#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // code that takes an hour to run
}

무시된 테스트만 실행

1
cargo test -- --ignored

unit test

단위테스트는 src디렉토리 내에 각 파일마다 테스트하는 코드들을 담는다. 일반적으로 각 파일마다 test라는 이름의 모듈을 만들고 #[cfg(test)] 어노테이션을 붙인다. cfg : 환경 설정(configuration)

비공개함수 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn internal() {
        assert_eq!(4, internal_adder(2, 2));
    }
}

integration test

src와 같은 디렉터리에 tests 디렉터리 생성.

1
2
3
4
5
6
7
//Filename: tests/integration_test.rs
extern crate adder;

#[test]
fn it_adds_two() {
    assert_eq!(4, adder::add_two(2));
}
1
2
3
4
$ cargo test
// 기존 테스트를 모두 실행
$ cargo test --test integration_test
// 통합테스트만 실행

I/O 프로젝트: 커맨드 라인 프로그램 만들기

std::env::args 프로그램에 전달된 커맨드라인 인자의 값을 얻는 함수 반복자

  1. 반복자는 하나의 연속된 값을 생성합니다.
  2. 반복자에 collect 함수 호출을 통해 반복자가 생성하는 일련의 값을 벡터로 변환할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();

    let query = &args[1];
    let filename = &args[2];

    println!("Searching for {}", query);
    println!("In file {}", filename);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use std::env;
use std::fs::File;
use std::io::prelude::*;

fn main() {
    let args: Vec<String> = env::args().collect();

    let query = &args[1];
    let filename = &args[2];

    println!("Searching for {}", query);
    println!("In file {}", filename);

    let mut f = File::open(filename).expect("file not found");

    let mut contents = String::new();
    f.read_to_string(&mut contents).expect("Something went wrong reading the file");

    println!("With text:\n{}", contents);
}

env::args().collect() 함수는 많은 종류의 콜렉션들과 사용될 수 있기 때문에, 우리가 원하는 타입이 문자열 벡터라고 args의 타입을 명시합니다.

가이드라인 프로세스

  1. 당신의 프로그램을 main.rs 과 lib.rs 로 나누고 프로그램의 로직을 lib.rs 으로 옮깁니다.
  2. 커맨드라인 파싱 로직이 크지 않으면, main.rs 에 남겨둬도 됩니다.
  3. 커맨드라인 파싱 로직이 복잡해지기 시작할거 같으면, main.rs 에서 추출해서 lib.rs 로 옮기세요.
  4. 이런 절차를 통해 main 함수에는 다음의 핵심 기능들만 남아있어야 합니다:
    • 인자 값들로 커맨드라인을 파싱하는 로직 호출
    • 다른 환경들 설정
    • lib.rs의 run 함수 호출
    • run이 에러를 리턴하면, 에러 처리.
  5. 인자 파서의 추출
  6. 설정 변수들을 그룹짓기
1
1
1
Posted 2021-01-10