Rust
≡ 목차 (Table of Contents)
러스트(Rust)는 컴퓨터 프로그래밍 언어 중 하나다. 주로 컴파일이 필요한 시스템 소프트웨어를 개발하기 위한 목적의 언어로 개발되었다.
초기에는 모질라 재단의 프로젝트에서 활용되었으며 독창적인 메모리 관리 체계의 우수성을 인정 받아서 다양한 프로젝트의 메인 언어로 활발하게 채용되고 있다.
주제별 글
- macOS에서 Rust 설치하기
- Cargo: Rust 프로젝트 및 패키지 의존성 관리자
- Rust의 시작점 main 함수
- Rust의 모듈 시스템
Primitive Types
Signed Integer
i8, i16, i32, i64, isize
Unsigned Integer
u8, u16, u32, u64, usize
Floating Points
f32, f64
Booleans
bool
Characters
char
char
can be cast to ant integer type, but only u8
can be cast to char
.
String
str, &str
&str
is a pointer type, but actually means a string type.
Variables and Constants
let decimal: i32 = 123; let immutable_variable = 10; // type inferences(i32) let mut mutable_variable = 20i64; // type inferences(i64), mutable mutable_variable = 30; // Constants const some_constant: u32 = 100; // Shadowing let immutable_variable = immutable_variable * 2; // Complex Initialization let x = { let y = 10; y * 2 // expression (without return) };
Option(Nullable, Optional)
let some_value = Some(5); let another_some_value: Option<i32> = None; if some_value.is_some() { ... } if some_value.contains(&5) { ... } if another_some_value.is_none() { ... } println!("some_value = {}", some_value.unwrap()); match another_some_value { None => println!("another_some_value has no value!"); Some(value) => { println!("another_some_value * 2 = {}", value * 2); } } if let Some(v) = some_value { println!("v = {}", v); } // function returns fn get_position() -> Option<i32> { if condition { Some(2) } else { None } }
Functions
fn function_name(x: i32, y: i64) -> u32 { ... loop { ... return 10; } 20 // expression (same with return 20) } let r = function_name(1, 2);
Control Flow
if-else
if n == 100 { ... } else if n > 100 { ... } else if n < 100 && n != 0 { ... } else { ... } let number = 10 let even = if number % 2 == 0 { true } else { false }
match
let r = match v { 0 | 1 => "binary", 2..=9 => "single integer", 10 => "ten", _ => "unknown" };
Loops
loop { // ... if condition { break; } } while condition { // ... } let values = [1, 2, 3, 4, 5]; for value in values.iter() { ... } for i in 0..10 { // i = 0 ~ 9 } for i in (0..4).rev() { // i의 값은 3, 2, 1, 0 순서 }
Ownership
다른 언어들과 비슷하게 포인터나 레퍼런스 개념이 사용되면 메모리 관리에 여러 문제가 생길 수 있는데 Rust는 소유권(ownership)이라는 좀 특이한 방식으로 메모리를 관리한다.
let s1 = String::from("foo bar"); let s2 = s1; // s2 owns s1 println!(s1); // Error!
위 코드 두 번째 라인은 s1 이 s2 로 이동(move)했다고 표현한다. 따라서 이 코드 이후 s1 을 액세스 하는 행위는 컴파일 에러로 막힌다.
소유권의 이전은 함수 호출의 매개변수로 넘어갈 경우도 동일하다.
some_function(s1);
위 코드는 some_function
함수가 s1 을 소유하게 된다. 그리고 이 함수가 종료될 때 s1 도 같이 사라지게 된다.
대신 반환(return)은 소유권도 반환한다.
let s1 = another_function(s1);
위 코드에서 s1 은 another_function
함수로 소유권이 넘어갔다가 반환되면서 다시 현재 스코프로 소유권이 반환된다. 물론 이 함수 내부에서 입력 받은 매개변수를 그대로 반환했을 경우이긴 하지만 말이다.
복제를 하지 않고 소유권을 빌려줄 수 있는 방법이 있다.
let s3 = &s1;
함수 매개변수로도 가능하다.
fn foo(s: &String) { ... }
다만 빌려주기만 할 뿐 불변(immutable)이기 때문에 읽기 참조만 가능하다.
참조에서 쓰기까지 가능하게 하려면 이렇게 할 수 있다.
let s3 = &mut s1;
가변 참조는 한 변수 당 하나 씩만 쓸 수 있다. 즉 하나의 포인터를 여러 곳에서 쓸(write) 수 없도록 제한하고 있다. 친절하다.
Structure
struct Human { name: String, family_name: String, age: u16 }
인스턴스 생성 시 초기화 하는 방법도 조금 생소하다.
let h = Human { name: String::from("Conrad"), family_name: String::from("Renn"), age: 20 };
생성 시 필드와 변수 이름이 동일한 경우에 한해서 아래와 같이 필드 이름을 생략하는 트릭이 있다.
fn create_human(name: String, family_name: String, age: u16) -> Human { Human { name, family_name, age } }
이 외에도 초기화 방법으로 동일하게 미리 할당된 변수의 값을 그대로 가져오는 방법이 제공된다.
let h2 = Human { name: String::from("James"), ..h };
이렇게 하면 name
을 제외한 나머지 데이터는 h
의 것을 이용해서 생성된다.
러스트의 구조체는 메서드 개념이 제공된다.
impl Human { fn birthday(&mut self) { self.age += 1; } // like as static method fn birth(name: String, family_name: String) -> Human { Human { name, family_name, age: 0 } } }
Enums
enum Gender { Male, Female, Other(String), Intersex(String, i32), Special { type: String, desc: String } Secret }
사용할 때는 이런 식이다.
let gender = Gender::Female;
match
로 값에 해당하는 분기를 쉽게 만들 수 있다.
match gender { Male => println!("He is male"); Female => println!("She is female"); Other(name) => println!("This is {}", name); Intersex(name, identifier) => println!("{} type identifier {}", name, identifier); _ => println!("It is so difficult!"); }
만약 하나 정도의 케이스만 일치시킨다면 이것 보다는 if let
을 쓰는 편이 편할 것 같다.
if let Other(name) = gender { ... }
개인적으로 익숙한 Swift랑 용도가 달라서 왠지 오해할 수도 있을 것 같다.
Results
enum Result<T, E> { Ok(T), Err(E) }
아는 사람은 알겠지만, Swift의 Result 타입과 거의 동일하다.
Complex and Collections
Tuple
let tup: (i32, i64, u8) = (500, 6.4, 1); let (x, y, z) = tup; let first = tup.0;
Python에서 가능한 튜플을 이용한 임시 변수 없는 교환(swap)이 가능할까 시험해 봤는데 문법 오류가 발생한다. 안타깝다.
Array
let list = [1, 2, 3, 4, 5]; let second = list[1]; let zeros = [0; 100]; // 0 x 100 elements let len_zeros = zeros.len();
Vector
let v1 = vec![1, 2, 3]; let v1second: &i32 = &v1[1]; let v1third: Option<i32> = v.get(2);
가변 벡터는 물론 추가로 다양한 가변 명령을 사용할 수 있다.
let mut v2 = Vec::new(); v2.push(1); v2.push(2); v2.push(3);
반복문에서 참조 및 변경이 가능하다.
for i in &v1 { ... } for i in &mut v2 { ... }
해시맵(Hash Map)
해시맵은 다른 언어들의 사전형(Dictionary)와 비슷하게 쓸 수 있는 Key - Value 콜렉션이다. 단지 원시 타입이 아니어서 모듈 임포트가 필요하다.
use std::collections:HashMap;
동적으로 해시맵을 생성하는 예는 이렇다.
let mut dict = HashMap::new(); dict.insert(String::from("apple"), String::from("사과")); dict.insert(String::from("banana"), String::from("버내너")); dict.insert(String::from("pear"), String::from("배"));
해시맵에 insert를 하는 과정에서 원시 타입 등은 복제가 진행되지 소유권 문제에서 자유로운 편이다.
값을 읽을 때는 Option 타입을 리턴함을 주의하자.
if let Some(banana) = dict.get(&String::from("banana")) { println!("banana -> {}", &banana); }
나열도 간단한 편이다.
for (key, value) in &dict { println!("{} -> {}", key, value); }
String
let s1 = "C String".to_string(); println!("length of s1 = {}", &s1.len()); let s1substr = &s1[1..4]; // -> " St" let s2 = String::from("Rust String"); let s4: String = "Some Rust String".into();
당연하겠지만 가변형도 지원된다.
let mut s3 = String::new(); s3.push('f'); // s3 -> "f" s3.push_str("oo"); // s3 -> "foo" s3 += &s1; // s3 -> "fooC String" s3 += &s2; // s3 -> "fooC StringRust String"
문자열 포맷은 println!
과 비슷하게 아래와 같은 식으로 쓸 수 있다.
let s4 = format!("s1 = {}, s2 = {}", s1, s2);
마치 Python의 format()
을 연상시키는 문법이다.
외부 링크
소개 및 팁
- 🌏Two core Unix-like utilities, sudo and su, are getting rewrites in Rust: sudo와 su가 Rust로 다시 쓰여지고 있다고 한다.
- 🌏Microsoft is busy rewriting core Windows code in memory-safe Rust: 마이크로소프트가 윈도우 커널을 Rust로 다시 쓰고 있다고 한다.
- 🌏The Rust Programming Language (한국어): 러스트의 전반적인 가이드 글의 한국어 번역판이다.
- 🌏🦀 러스트의 멋짐을 모르는 당신은 불쌍해요: 러스트를 소개하는 글이다.
- 🌏Rust: Dropping heavy things in another thread can make your code 10000 times faster: 메모리 해제를 특정 스레드에서 하게 해서 레이턴시를 개선시키는 기술에 대한 이야기다. 10000배 빨라진다는 것은 그냥 해제로 발생하는 레이턴시 개선 수준 이야기지 그렇게 많이 빨라지는 것은 아닐 것 같다. 어쨌거나 러스트의 메모리 관리에서나 가능한 특수한 메모리 관리 기법이다.
사용기
- 🌏Memory Safe Languages in Android 13: 안드로이드 팀에서 Rust를 도입한 후 C 언어 코드 비율에 이를 정도로 많은 대체가 이뤄졌지만 이로 인한 메모리 취약점은 하나도 없다고 한다. Rust의 메모리 안전제일(memory-safe) 특징이 실제로 취약점 감소에도 도움이 되는 것으로 보인다.
- 🌏WebAssembly: TinyGo vs Rust vs AssemblyScript: WebAssembly로 빌드하기 위해서 어떤 언어가 좋을지 몇 가지 언어로 비교한 내용. 여기서는 Rust가 가장 효율적이었다.
- 🌏3K, 60fps, 130ms: achieving it with Rust: 러스트로 성능을 얼마나 개선했는지와 어떤 패키지가 유용했는지 등을 볼 수 있다.
- 🌏Learn Rust the Dangerous Way: C 코드와 Rust 코드 비교로 Rust를 공부할 수 있는 사이트
- 🌏Four Years of Rust At OneSignal: 어떤 기업의 러스트 사용기 및 장단점을 정리한 글이다.
- 🌏Rust is Surprisingly Good as a Server Language: 그냥 러스트를 찬양(?)하는 글이다.
Written by Rust
- 🌏GitUI: Blazing 💥 fast terminal-ui for git written in rust 🦀