Rust를 처음사용해봤을때 살짝 놀랐습니다. 그동안 C++이 요즘 사용하는언어중 가장 엄격한 언어라고 생각했는데, Rust는 기본적으로 변수를 선언하면 값을 바꿀수없습니다. C++에서는 보통 변수를 선언할떄 int = 0; 이라 하면 이후에 int = 10; 이나 다른값으로 변경이 가능합니다. 그리고 상수(constant) 앞으로 변하지않을 변수에는 const int = 0; 이런식으로 키워드를 붙여주죠. 반면, Rust는 let = 0; 이라고 선언하면 이건 이후에 값을 바꿀수가 없습니다. C++ 에서 const int = 0; 이라고 선언한것과 같죠. 만약 이후에도 값이 변할수있는 변수를 선언하려면 let mut num = 0; 이라고 선언해줘야합니다. mut = mutatable 변할수있다고 컴파일러에게 알려주는것이죠. 이는 C++에서 사용자가 상수변수를 반들면서 const를 안붙여도 잘 작동하기에 짧은 int = 0; 방식으로 변수를 선언하는 경우가 많아서 이 변수를 수정하면 안되는데 실수로 수정하게 될경우 에러가나는것을 방지해줍니다. Rust방식이 단점조차 없는 무조건 더 좋은 방식이다 라고 말씀드리는것은 아니고 Rust 언어의 철학을 느낄수있는 부분이었습니다.
rust-analyzer
Rust analyzer는 여러기능을 하는데 컴파일을 하기전 코드 작성중 실시간으로 코드를 정검해줌으로 개발의 편의성을돕고 언어 공부를 쉽게해줍니다. 변수 관련글에 이걸 먼저 소개한 이유가있습니다. C++ 은 변수선언할때 자료형을 int String float등 명시하여 선언합니다. JS나 파이썬같은 언어는 let 이나 var로 어떤 자료형이든 신경쓰지않고 만들수 있어서 편리함을 갖고있습니다. C++도 2010년쯤 auto라는 변수 선언 키워드를만들어 어떤자료형도 담을수있는 키워드를 만들었지요. Rust는 let으로 변수를 선언하는데 rust-analyzer가 자료형을 자동으로 감지해 표시해줍니다. 아래 이미지를 보시면 변수에 문자열을 대입하면 String이 자동으로 생겨나고, 10을 대입하니 i32가 자동으로 생기는걸 알수있습니다. let 하나로 편하게 변수를 만들고 나중에 코드를 볼때는 : i32가 따라다니면서 자료형을 알려주니까 두가지 장점을 모두 갖고있는 셈이죠.

자동으로 생성되어 위 어둡게 표시된 : String과 : i32 부분을 타입 추론 시각화 라고 하는데 직접 명시해줄수도 있습니다. 예를들어 변수에 String이라고 명시해줬는데 값은 숫자를 넣으려한다면 둘중하나는 틀렸으니 컴파일러가 에러를 보여줄겁니다.
자료형 (Data Type)
정수형 (Integer)
부호있는 정수: i8, i32, i64, i128
부호 없는 정수:u8, u32, u64, u128
isize, usize: CPU아키텍쳐에따른 정수형 변수
부동 소수점 (Float, Double)
f32, f64
불리언 (Boolean)
true, false
문자형 (Character)
char // 4바이트, 유니코드 지원
가변 메모리 변수(Heap)
String:new() (문자열 길이에따라 메모리 크기가 변하는 Heap 메모리 사용)
Vec (벡터 가변 배열)
문자열
let mut guess = String::new();
guess = "하이하이"; // 에러!
문자열 수정할 때 꼭 알아야 하는 부분(1)
아래처럼 문자를 수정하는데 1번방식은 새 string 객체로 교체하기때문에 기존 힙메모리는 해제하고 새 객체를 위해 새로운 힙메모리를 사용합니다. 두번째 방법은 기존메모리를 해제하지않고 내용만 지우고 하이하이로 교체합니다. 이것이 비용이 훨씬 적고 빠릅니다.
let mut guess = String::new();
// 1. 아예 새로운 String 객체로 갈아치우기
guess = String::from("하이하이");
// 또는
guess = "하이하이".to_string();
// 2. 기존 String 뒤에 내용 추가하기 (보통 이걸 더 많이 씀)
guess.clear(); // 기존 내용 비우기
guess.push_str("하이하이");
문자열 수정할 때 꼭 알아야 하는 부분(2)
다른변수에 있는값을 가져올때는 아래처럼 소유권 이동방식을 사용합니다.
let other_str = String::from("데이터가 아주 큼");
let mut guess = String::new();
// 소유권 이동 (Move)
guess = other_str;
// 이제 other_str은 사용할 수 없음!
Rust의 메모리관리
C++에서는 힙메모리를 사용하면 free()나 delete를 통해 메모리 해제를 해야했습니다. JS, Python은 가비지 콜렉터가있어서 자동으로 청소가됩니다 Rust는 새로운 소유권 개념을 적용하였습니다. 이 소유권개념은 C++처럼 수동으로 메모리해제를 하지않으면서도, 포인터를 사용하지않으면서도 데이터복사가 일어나는걸 최소화하는데 도움이 됩니다. 소유권 개념이 어떻게 작동하는지 자세히 알아보겠습니다.
소유권 (Ownership)
이동(move)
String은 문자열입니다 크기가 2bytes일수도있고 kb단위로 커질수도있겠죠. 이런건 heap메모리를 사용하고 새로 공간확보하고 지우고하는데 비용이 큽니다. Rust에서는 아래처럼 문자열을 다른변수에 대입하려하면 복사가 일어나지않고 소유권이 이동합니다. 같은코드를 C++에서 작성하면 s1에 “hello”라는 5바이트 데이터가 저장되었다가 s2=s1 을 하는순간 추가로 5바이트를 확보하며 s2, s1 두변수에서 총 10bytes를 추가로 사용합니다. Rust에서는 소유권을 이동해버려서 굳이 s1이 이후에 또 사용이 필요한지 알수없는 상황에서 이를 유지하지 않는것이죠.
let s1 = String::from("hello");
let s2 = s1; // s1의 소유권이 s2로 '이동'함
//Rust에서는 s1이 소유권을 잃고나서 값을 읽으려고하면 에러가 발생합니다.
// println!("{}", s1); // 에러! s1은 더 이상 값을 가지고 있지 않음.
복사(copy)
정수형같은경우는 용량이 작아서 기본적으로 복사가 됩니다. 또한 스택메모리를 사용하여 메모리확보 비용도없습니다.
let x = 5;
let y = x; // x의 값이 복사되어 y에 들어감
println!("x: {}, y: {}", x, y); // 둘 다 사용 가능!
빌림 (Borrowing) 과 참조(&)
값을 함수를 통해 전달하고 연산할때는 참조개념을 이용하는데 사용방법은 아래와 같습니다. 함수 인자로 &를 넣으면 Rust용어로 빌려 준다는 개념이되고, 함수에서는 &가 붙은 매개변수가 있다면 빌려온변수, 참조변수 라고 부를 수 있습니다.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // s1의 참조 전달
println!("'{}'의 길이는 {}입니다.", s1, len); // s1 여전히 사용 가능!
}
fn calculate_length(s: &String) -> usize { // &String: 소유권은 안 받고 빌리기만 함
s.len()
}
불변 참조(&T)는 여러 개 가질 수 있다. (여러 명이 동시에 읽기 가능)
fn main() {
let s = String::from("hello");
let r1 = &s; // 첫 번째 읽기 전용 대여
let r2 = &s; // 두 번째 읽기 전용 대여
let r3 = &s; // 세 번째 읽기 전용 대여
println!("{}, {}, 그리고 {}", r1, r2, r3);
// ✅ 모두 성공! 아무 문제 없습니다.
}
가변 참조(&mut T)는 딱 하나만 가질 수 있다. (쓰는 사람은 한 명이어야 함)
fn main() {
let mut s = String::from("hello");
let r1 = &mut s; // 첫 번째 쓰기 권한 대여
// let r2 = &mut s; // ❌ 에러! 이미 r1이 빌려갔는데 또 빌릴 수 없음
r1.push_str(", world");
println!("{}", r1);
}
//Rust에서 문자열을 입력받을때에도 가변 참조를 사용합니다.
let mut text= String::new();
io::stdin().read_line(&mut text).expect("잘못입력됨");
// &mut text라고해서 문자열변수를 가변참조로 전달하고있죠.
불변 참조와 가변 참조를 동시에 가질 수 없다. (읽고 있는데 누가 값을 바꾸면 안 됨)
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 읽기 전용으로 빌림 (불변 참조)
let r2 = &s; // 또 읽기 전용으로 빌림 (불변 참조)
// let r3 = &mut s; // ❌ 에러! r1, r2가 '변하지 않을 것'이라 믿고 읽는 중인데
// 갑자기 수정 권한(&mut)을 주면 데이터 일관성이 깨짐
println!("{}와 {}", r1, r2);
}
Rust의 변수명
변수명은 영어와 숫자 언더스코어_ 만 사용가능한것 은 다른 언어와 비슷합니다. Rust에서는 특별한 기능이 하나있는데 변수명 시작할때 _를 붙이면 이변수는 사용할수도 안할수도 있으니 사용하지않아도 경고를 발생시키지 않게 컴파일러에게 알려줄수 있습니다.
fn main() {
let x = 5; // ⚠️ 경고: unused variable: `x`
let _y = 10; // ✅ 통과: 경고가 뜨지 않음
}