Rust[작성중]
Rust 정리
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!!!");
}
소유권
소유권 규칙
- 러스트의 각각의 값은 해당값의 오너(owner)라고 불리우는 변수를 갖고 있다.
- 한번에 딱 하나의 오너만 존재할 수 있다.
- 오너가 스코프 밖으로 벗어나는 때, 값은 버려진다(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
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() {
}
비공개 규칙
- 만일 어떤 아이템이 공개라면, 이는 부모 모듈의 어디에서건 접근 가능합니다.
- 만일 어떤 아이템이 비공개라면, 같은 파일 내에 있는 부모 모듈 및 이 부모의 자식 모듈에서만 접근 가능합니다.
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에 접근불가. 다음의 방법으로 접근가능.
- ::client::connect();
- 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) 반환 값에 대한 라이프타임
컴파일러가 라이프타임을 추론하는 규칙
- 참조자인 각각의 파라미터는 고유한 라이프타임 파라미터를 갖습니다. 바꿔 말하면, 하나의 파라미터를 갖는 함수는 하나의 라이프타임 파라미터를 갖고: fn foo<’a>(x: &’a i32), 두 개의 파라미터를 갖는 함수는 두 개의 라이프타임 파라미터를 따로 갖고: fn foo<’a, ‘b>(x: &’a i32, y: &’b i32), 이와 같은 식입니다.
- 만일 정확히 딱 하나의 라이프타임 파라미터만 있다면, 그 라이프타임이 모든 출력 라이프타임 파라미터들에 대입됩니다: fn foo<’a>(x: &’a i32) -> &’a i32.
- 만일 여러 개의 입력 라이프타임 파라미터가 있는데, 메소드라서 그중 하나가 &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) 함수 테스트 함수는 세가지 동작을 수행
- 필요한 데이터 혹은 상태를 설정하기
- 우리가 테스트하고 싶은 코드를 실행하기
- 그 결과가 우리 예상대로인지 단언하기(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 프로그램에 전달된 커맨드라인 인자의 값을 얻는 함수 반복자
- 반복자는 하나의 연속된 값을 생성합니다.
- 반복자에 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의 타입을 명시합니다.
가이드라인 프로세스
- 당신의 프로그램을 main.rs 과 lib.rs 로 나누고 프로그램의 로직을 lib.rs 으로 옮깁니다.
- 커맨드라인 파싱 로직이 크지 않으면, main.rs 에 남겨둬도 됩니다.
- 커맨드라인 파싱 로직이 복잡해지기 시작할거 같으면, main.rs 에서 추출해서 lib.rs 로 옮기세요.
- 이런 절차를 통해 main 함수에는 다음의 핵심 기능들만 남아있어야 합니다:
- 인자 값들로 커맨드라인을 파싱하는 로직 호출
- 다른 환경들 설정
- lib.rs의 run 함수 호출
- run이 에러를 리턴하면, 에러 처리.
- 인자 파서의 추출
- 설정 변수들을 그룹짓기
1
1
1