fn main() {
println!("Hello World!");
}
Rust의 모토인 무비용 추상화(zero-cost abstraction)
소유권(Ownership) 시스템
let x: f64 = 3.14159;(타입명시) == let x = 3.14159f64;(리터럴 표현)
snake_caselet mut x = 42;boollet bv = true;u8 u16 u32 u64 u128let a = 42u8;i8 i16 i32 i64 i128let a = 42i8;usize isizelet a = 42usize;f32 f64let a = 3.14f32;(값, 값, ...)let a: (&str, i32, bool) = ("Alice", 30, true);[값, 값, ...]let a: [&str; 5] = ["월", "화", "수", "목", "금"];let a = [1, 2, 3, 4, 5];let slice = &a[1..4]; // [2, 3, 4]str(문자열 slice) - 런타임에 길이가 정해지는 텍스트let a: &str = "문자열";let a = 42u8;
let a = a as u32
const PI = 3.14159f32;
SCREAMING_SNAKE_CASElet a: [i32; 3] = [1, 2, 3];
[T;N]로 표현하며, 여기서 T는 원소의 자료형, N은 컴파일 타임에 주어지는 고정된 길이[x] 연산자로 가져올 수 있는데, 여기서 x는 당신이 원하는 원소의 (0에서부터 시작하는) usize 형의 인덱스fn add(x: i32, y: i32) -> i32 {
return x + y;
}
snake_caselet (a, b) = swap(321, 123);
fn swap(x: i32, y: i32) -> (i32, i32) {
return (y, x);
}
// 리턴 값의 튜플을 리턴
let result = swap(123, 321);
println!("{} {}", result.0, result.1);
// 튜플을 두 변수명으로 분해
let (a, b) = swap(result.0, result.1);
println!("{} {}", a, b)
return (y, x);return ();if/else if/else
if x < 0 {
} else if x == 0 {
} else {
}
\==, !=, <, >, <=, >=, !, ||, &&.loop {
x += 1;
if x == 42 {
break;
}
}
let v = loop {
x += 1;
if x == 13 {
break "13 찾았다";
}
};
while은 반복문에 조건을 간단히 넣을 수 있게 해준다.while x != 42 {
x += 1;
}
for x in 0..5 {
println!("{}", x);
} // 0~5 '전'까지의 숫자들을 생성 하며 {}를 반복한다.
결과:
0
1
2
3
4
for x in 0..=5 {
println!("{}", x);
} // 0~5 까지의 숫자들을 생성 하며 {}를 반복한다.
결과:
0
1
2
3
4
5
.. 연산자는 시작 숫자에서 끝 숫자 전까지의 숫자들을 생성하는 반복자를 만든다...= 연산자는 시작 숫자에서 끝 숫자까지의 숫자들을 생성하는 반복자를 만든다.match의 조건은 빠짐 없이 모든 케이스를 처리해야 한다.let (a, b) = (구조체, 열거형, 튜플))의 조합은 현재까지 Rust에서 가장 흔하게 사용하는 패턴이다.match x {
0 => {
println!("0 발견");
}
// 여러 개 값과 대조할 수 있다
1 | 2 => {
println!("1 또는 2 발견!");
}
// 범위로 대조할 수 있다
3..=9 => {
println!("3에서 9까지의 숫자 발견");
}
// 찾은 숫자를 변수에 바인딩할 수 있다
matched_num @ 10..=100 => {
println!("10에서 100까지의 숫자 {} 발견!", matched_num);
}
// 모든 케이스가 처리되지 않았을 경우 반드시 존재해야 하는 기본 match
_ => {
println!("뭔가 다른거 발견!");
}
}
let a = if x < 0 { -1 } else { 1 };
if, match, 함수, 또는 범위 블록의 마지막 구문에 ';'가 없다면 Rust는 그 값을 블록의 리턴 값으로 간주한다.let food = "햄버거";
let result = match food {
"핫도그" => "핫도그다",
// 리턴문 하나 뿐이라면 중괄호는 필수가 아님
_ => "핫도그가 아니다",
};
결과: result == "핫도그가 아니다";
let v = {
// 이 범위 블록은 함수 범위를 더럽히지 않고 값을 가져오게 해준다
let a = 1;
let b = 2;
a + b
};
v == a + b
struct는 필드(field)들의 collectionstruct SeaCreature {
// String은 struct다
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
:: 연산자를 이용하여 호출String::from("HI!");. 연산자를 이용하여 호출a.len();데이터 메모리(data memory) - 크기가 고정 되었으며 static (i.e. 프로그램이 실행되는 동안 항상 사용 가능)한 데이터용.
이런 종류의 데이터는 컴파일러가 많은 최적화를 하며, 위치가 알려져 있고 고정되어 있기 때문에 일반적으로 사용하기에 매우 빠르다고 여긴다
"Hello World!" 이 텍스트의 바이트들은 오직 한 곳에서만 읽히므로 이 영역에 저장될 수 있습니다.
스택 메모리(stack memory) - 함수 내에서 변수로 선언되는 데이터용.
이 메모리의 위치는 함수 호출 동안에는 절대 변하지 않기 때문에 컴파일러가 코드를 최적화할 수 있으며, 이로 인해 접근하기에 매우 빠릅니다.
let a = 0;
힙 메모리(heap memory) - 애플리케이션이 실행되는 동안 생성되는 데이터용.
이 영역의 데이터는 추가하거나, 이동하거나, 제거하거나, 크기를 바꾸거나, 등을 할 수 있습니다.
이런 동적 속성 때문에 일반적으로 사용하기에 느리다고 여기지만, 훨씬 더 창의적인 메모리 사용이 가능합니다.
데이터가 이 영역에 추가되면 할당(allocation)이라고 부릅니다.
데이터가 이 영역에서 제거되면 해제(deallocation)라고 부릅니다.
let mut a = Vec::new();
StructName { ... }. 연산자를 통해 접근한다.struct SeaCreature {
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
// SeaCreature의 데이터는 stack에 있음
let ferris = SeaCreature {
// String struct도 stack에 있지만,
// heap에 있는 데이터에 대한 참조를 갖고 있음
animal_type: String::from("게"),
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("집게"),
};
println!(
"{}는 {}이다. {}개의 팔과, {}개의 다리와, {}를 무기로 갖고 있다.",
ferris.name, ferris.animal_type, ferris.arms, ferris.legs, ferris.weapon
);
}
Ferris는 게이다. 2개의 팔과, 4개의 다리와, 집게를 무기로 갖고 있다.
*Tour of Rust에서 발췌
struct Locatuon(i32, i32);
// 이것도 여전히 stack에 있는 struct임
let a = Location(42, 32);
()의 또 다른 이름이다. => Unit(빈 tuple)struct Macker;let a = Markerenum 키워드를 통해 몇 가지 태그된 원소의 값을 가질 수 있는 새로운 자료형을 생성match는 모든 가능한 enum 값을 빠짐없이 처리할 수 있도록 함#![allow(dead_code)] // 이 줄은 컴파일러 경고를 방지해줌
enum Species {
Crab,
Octopus,
Fish,
Clam,
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
let ferris = SeaCreature {
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("claw"),
};
match ferris.species {
Species::Crab => println!("{}는 게이다", ferris.name),
Species::Octopus => println!("{}는 문어이다", ferris.name),
Species::Fish => println!("{}는 물고기이다", ferris.name),
Species::Clam => println!("{}는 조개이다", ferris.name),
}
}
enum의 원소들은 C의 union 처럼 동작할 수 있도록 한 개 이상의 자료형을 가질 수 있다.enum이 match를 통해 패턴 일치될 때, 각각의 데이터 값에 변수명을 붙일 수 있다.enum은 tagged union 으로도 알려져 있다.#![allow(dead_code)] // 이 줄은 컴파일러 경고를 방지해줌
enum Species {
Crab,
Octopus,
Fish,
Clam,
}
enum PoisonType {
Acidic,
Painful,
Lethal,
}
enum Size {
Big,
Small,
}
enum Weapon {
Claw(i32, Size),
Poison(PoisonType),
None,
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: Weapon,
}
fn main() {
// SeaCreature의 데이터는 stack에 있음
let ferris = SeaCreature {
// String struct도 stack에 있지만,
// heap에 있는 데이터에 대한 참조를 갖고 있음(String::from())
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: Weapon::Claw(2, Size::Small),
};
match ferris.species {
Species::Crab => match ferris.weapon {
Weapon::Claw(num_claws, size) => {
let size_description = match size {
Size::Big => "큰",
Size::Small => "작은",
};
println!(
"ferris는 {}개의 {} 집게를 가진 게이다",
num_claws, size_description
)
}
_ => println!("ferris는 다른 무기를 가진 게이다"),
},
_ => println!("ferris는 다른 동물이다"),
}
}
struct나 enum을 부분적으로 정의하여, 컴파일러가 컴파일 타임에 코드 사용을 기반으로 완전히 정의된 버전을 만들 수 있게 해준다.struct BagOfHolding<T> {
item: T,
}
Rust의 제네릭은 C++의 템플릿과 비슷하지만, 타입 안전성을 위해 제약 조건(`trait bounds`)을 명시적으로 요구하며, 컴파일러가 타입의 유효성을 더 엄격하게 검사한다.fn print_area<T: HasArea>(shape: T) { // <T: HasArea>가 trait bounds이다.
println!("The area is {}", shape.area());
}
Turbofish로 알려진 ::<T> 연산자를 사용해 자료형을 명시할 수 있다.// 일부만 정의된 struct 자료형
struct BagOfHolding<T> {
item: T,
}
fn main() {
// 중요: 여기서 generic 자료형을 쓰면, 컴파일 타임에 생성된 자료형을 생성하게 됨.
// Turbofish를 쓰면 명시적일 수 있다.
let i32_bag = BagOfHolding::<i32> { item: 42 };
let bool_bag = BagOfHolding::<bool> { item: true };
// Rust는 generic에도 자료형을 유추할 수 있다!
let float_bag = BagOfHolding { item: 3.14 };
// 중요: 실생활에서는 가방 속에 가방을 넣지 마시오
let bag_in_bag = BagOfHolding {
item: BagOfHolding { item: "쾅!" },
};
println!(
"{} {} {} {}",
i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item
);
}
null 키워드는 값이 없음을 나타낸다.null이 없다.None을 사용한다.enum Item {
Inventory(String),
// None은 항목의 부재를 나타냄
None,
}
struct BagOfHolding {
item: Item,
}
Option
null을 쓰지 않고도 nullable한 값을 표현할 수 있는 Option이라 불리는 내장된 generic enum이 있다.enum Option<T> {
None,
Some(T),
}
언제나 Some과 None로 Option을 인스턴스화(생성)할 수 있다.
// 일부만 정의된 struct 자료형
struct BagOfHolding<T> {
// 자료형 T의 인자는 다른 곳으로 넘겨질 수 있음
item: Option<T>,
}
fn main() {
// 중요: i32를 위한 가방에 아무 것도 안들었다!
// Rust가 무슨 자료형의 가방인지 알 수가 없으므로 자료형을 지정해야 함.
let i32_bag = BagOfHolding::<i32> { item: None };
if i32_bag.item.is_none() {
println!("가방에 아무 것도 없다!")
} else {
println!("가방에 뭔가 있다!")
}
let i32_bag = BagOfHolding::<i32> { item: Some(42) };
if i32_bag.item.is_some() {
println!("가방에 뭔가 있다!")
} else {
println!("가방에 아무 것도 없다!")
}
// match는 Option을 우아하게 분해하고, 모든 케이스를 처리하도록 해준다!
match i32_bag.item {
Some(v) => println!("가방에서 {}를 찾았다!", v),
None => println!("아무 것도 찾지 못했다"),
}
}
Result
Result라 불리는 내장된 generic enum가 있다.enum Result<T, E> {
Ok(T),
Err(E),
}
언제나 Ok와 Err로 Result을 인스턴스화(생성)할 수 있다.
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("맞는 숫자가 아닙니다"))
}
}
fn main() {
let result = do_something_that_might_fail(12);
// match는 Result를 우아하게 분해하고, 모든 케이스를 처리하도록 해준다!
match result {
Ok(v) => println!("{} 발견", v),
Err(e) => println!("오류: {}", e),
}
}
fn main() -> Result <(), String> {
// 모든 일이 잘 끝났음을 표현하기 위해.
// Result Ok 안에 unit 값을 쓰고 있는걸 잘 봐두십시오.
Ok(())
}