안녕하세요, 단테입니다.
2022년 한 해동안 러스트에 대한 관심이 국내외로 뜨거웠습니다.
러스트는 커뮤니티가 활발하고 공식문서가 잘 정리되어 있기 때문에 새로운 개발자가 쉽게 접근할 수 있습니다. 정적 타입 시스템과 메모리 안정성을 제공하기 때문에 안정적인 코드를 작성할 수 있으며 컴파일 시간이 빠르고 코드의 성능을 극대화 할 수 있다는 프로그래밍 언어입니다.
러스트 언어를 활용해 만들 수 있는 분야는 다양합니다.
웹 어셈브리를 사용한다면 JS를 사용하지 않고도 브라우저 상에서 작동하는 앱을 오직 러스트로만 작성할 수 있으며, JS 보다 높은 성능 높은 앱을 만들 수 있습니다. 피그마 또한 러스트가 활용되었으며
SWC, STC등 러스트의 성능상 이점을 이용한 개발을 위한 툴들도 많이 제작되고 있습니다.
JS/TS에서 더 나아가 더 나은 개발자로 성장하기 위해 금년도부터 러스트 공부를 시작하겠습니다.
본 비욘드 JS: 러스트
시리즈에서 다루는 러스트 학습 내용은 무료 영문 자료인 The Rust Programming Language
에서 가져왔습니다.
러스트의 변수는 mut
키워드로 선언하지 않으면 기본적으로 immutable입니다.
여기서 immutability
란 JS에서 ES6 부터 등장한 let , const 키워드를 사용해 설명하자면,
let을 통해 선언한 변수는 mutable , const를 통해 선언한 변수는 immutable입니다.
cargo run
은
NODE.js에서 node main.js
와 같이 코드를 실행시킬 때 사용하는 명령어로 러스트로 작성한 코드를 컴파일 해줍니다.
다음 코드를 cargo run
명령어를 통해 빌드 할 시 오류가 발생하는 것을 알 수 있습니다.
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` due to previous error
js의 let과 동일하게 변수를 변경하기 위해서는 mut 키워드를 사용합니다.
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
러스트에서는 variable과 constant가 엄격하게 분류되어있습니다.
immutable variable과 마찬가지로 한번 선언된 이후에는 변경이 불가능한데요, let var
과 const var
은 어떤 차이점이 있을까요?
mut
키워드를 사용할 수 없습니다.러스트는 정적 타입 언어입니다.
코드 작성 시 컴파일러에서 타입을 추론할 수 있는데요,
variable 선언 시 러스트 컴파일러가 자동으로 타입을 추론하는 것과 다르게 constants 사용 시 명시적으로 타입을 선언해주어야 합니다.
TS의 const 사용과 다른 점입니다.
// rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
node.js 환경에서 constants.ts
와 같은 파일에 모든 상수를 선언해두고 앱 전체에서 불러 사용하는 것과 동일하게 러스트의 const 또한 선언된 스코프 내부에서는 어디에서도 사용할 수 있습니다.
js와 다르게 러스트에서는 동일한 변수명을 여러 번 let
keyword와 함께 선언할 수 있습니다.
이를테면 다음의 코드는 js 환경에서는 에러가 발생합니다.
// js
let name = "dante";
let name = "Dante";
러스트에서는 Shadowing이라는 특성이 있습니다. 앞서 봤던 JS 코드에서 소문자 단테와 대문자 단테를 특정 콘텍스트에서 동시에 사용하기 위해서는 다른 변수명으로 선언해야 합니다.
// js
const name_lower = "dante";
const name_upper = "Dante";
하지만 러스트에서 제공하는 Shadowing을 사용 시 동일한 변수 명을 재선언할 수 있습니다.
fn main() {
let x = 5; // < - 1
let x = x + 1; // < - 2
{
let x = x * 2; // < - 3
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}
위에서 x가 세번에 걸쳐서 선언된 것을 볼 수 있습니다.
쉐도잉을 사용할 경우 위에서 선언한 변수가 재선언 되기 전의 라인까지만 유효하기 때문에 코드를 실행시키면 다음과 같이 결과가 출력됩니다.
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
쉐도잉을 사용하는 것과 mut 키워드를 사용하는 것은 다음과 같은 차이점이 있습니다.
아래 코드는 쉐도잉을 통해 두번째 선언된 let 이후의 값을 spaces.len()
으로 사용하는 것이지 첫번 째 줄의 spaces의 값을 변경하는 것이 아닙니다.
let spaces = " "; // string
let spaces = spaces.len(); // i32
또한 mut 키워드 사용과 다르게 전혀 다른 타입의 값을 바인딩하는 것 또한 가능합니다. 만약 mut 키워드를 사용해 위의 코드를 작성했다면 아래와 같은 에러가 발생할 것입니다.
let mut spaces = " ";
spaces = spaces.len();
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
--> src/main.rs:3:14
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` due to previous error
spacesLen이라는 변수명을 별도로 작명하지 않고도 spaces라는 변수명을 재사용할 수 있다는 장점이 있습니다.
이제 자료형에 대해 알아보겠습니다.
자바스크립트와는 어떤 차이점이 있는지 살펴봅시다.
러스트에서 사용되는 모든 값은 특정 타입을 가지고 있습니다.
러스트는 정적 타입 언어이기 때문에 컴파일 타임에 값의 타입을 파악할 수 있습니다.
러스트 컴파일러는 많은 경우 타입스크립트 컴파일러와 동일하게 타입을 명시적으로 선언하지 않아도 추론하 수 있지만 꼭 명시적으로 작성해주어야 하는 경우도 있습니다.
String
타입을 Numeric
타입으로 변경하는 아래의 예시와 같은 경우입니다.
이 경우에 우리는 명시적으로 guess 병수에 u32
타입을 지정해주어야 합니다. 아래에서는 지정해주지 않았습니다.
let guess = "42".parse().expect("Not a number!"); // X
// let guess = "42".parse().expect("Not a number!"); // O
$ cargo build
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
error[E0282]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ consider giving `guess` a type
For more information about this error, try `rustc --explain E0282`.
error: could not compile `no_type_annotations` due to previous error
스칼라 타입은 자바스크립트의 Primitive Types와 유사합니다.
정수
타입은 표현하는 숫자 크기에 따라 타입 지정이 다릅니다.
자바스크립트와 두드러지는 차이점인데요, u
는 unsigned
, i
는 signed
를 의미합니다.
image: https://doc.rust-lang.org/stable/book/ch03-02-data-types.html
i8, u8이 표현할 수 있는 비트는 총 8비트이기 때문에 unSigned, signed가 표현할 수 있는 최대 숫자도 차이가 있습니다.
i8
의 경우 -(2의 8-1 제곱)
부터 (2의 8-1 제곱) -1
까지 표현할 수 있는 반면
u8
의 경우 음수를 표현하지 않아도 되므로 0부터 (2의 8제곱) -1
까지 표현할 수 있죠.
위의 표에서 isize
타입은 사용하는 컴퓨터 아키텍쳐에 따라 자동으로 정해집니다.
32 bit 계열에서는 i32, 64 bit 계열에서는 i64가 채택되죠.
js와 동일하게 _
언더 스코어를 이용해 십진수를 표현할 수 있습니다. 아래표를 참고해주세요.
대부분의 유즈케이스에서 i32를 사용하기 때문에 어떤 정수형을 선언해야 할지 모르겠다면 i32를 사용하는 것이 가장 일반적인 방법입니다.
러스트는 자바스크립트와 다르게 부동소수점 형 숫자 타입을 별도로 가지고 있습니다.
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
연산 표기는 자바스크립트와 다를게 없습니다.
fn main() {
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // Results in -1
// remainder
let remainder = 43 % 5;
}
러스트에서 참/거짓을 나타내는 불린 타입은 1바이트의 크기를 가지고 있습니다.
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
자바스크립트에서는 문자열 타입을 string으로 퉁치는 반면 러스트는 보다 세분화 합니다.
c언어의 char 타입과 동일하게 단일 글자를 가르키는 러스트의 타입은 Character 라고 부르며 char
로 선언됩니다.
아래와 같이 사용됩니다.
fn main() {
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
}
러스트에서 single quote '
와 double quotes "
는 큰 차이를 빚습니다.
바로 single quote를 사용해야 러스트 컴파일러에게 Character 타입을 사용하고 있음을 알릴 수 있다는 것인데요, 신기한 것은 러스트의 char 타입은 4바이트를 차지합니다. 아스키코드 보다 훨씬 많은 글자를 표현할 수 있습니다. 중국어, 일본어, 한국어, 이모지까지요.
러스트의 Compound Type은 튜플과 배열이 있습니다.
자바스크립트에서는 튜플 타입이 없지만 러스트에서는 지원하며, 타입스크립트에서 배열을 튜플형식으로 사용하는 것과 다르게 러스트에서는 별도의 타입으로 존재합니다.
튜플은 여러 개의 타입으로 이뤄질 수 있는데요, 참조하는 문법이 배열과 상이합니다.
자바스크립트의 배열과 다르게 []
가 아닌 ()
를 통해 선언되었습니다.
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
구조분해를 통해 각 튜플 안에 선언된 값을 가져올 수 있습니다.
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");
}
튜플 값의 참조는 자바스크립트의 객체 키 값 참조와 같이 .
를 사용합니다.
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
러스트의 배열은 자바스크립트의 배열과는 다소 다릅니다. 맨 처음 선언된 길이 만큼의 요소만 가질 수 있으며 모든 요소의 타입은 동일해야 합니다.
예를 들어 정수 5개를 갖는 array
타입은 다음과 같이 표기할 수 있습니다.
let x: [i32; 5] = [1, 2, 3, 4, 5];
생소한 타입 선언이 나왔습니다. i32 타입을 5개 만큼 가지는 변수이기 때문에
[i32; 5]
라고 선언했습니다.
러스트의 array 타입은 [T; N]
형식으로 작성되며, T는 요소의 타입을, N은 요소의 개수를 의미합니다. array 타입은 상수 값을 저장하는 데 적합합니다.
array
타입은 개수가 고정되어 있기 때문에 새로운 요소를 추가하거나 제거할 수 없습니다.
하지만 인덱스를 사용해 각 요소에 접근하거나 새로운 값으로 대체할 수 있습니다.
let x: [i32; 5] = [1, 2, 3, 4, 5];
x[2] = 10;
println!("{:?}", x); // [1, 2, 10, 4, 5]
아래 코드에서 각 요소를 인덱스로 접근해보겠습니다.
let x: [i32; 5] = [1, 2, 3, 4, 5];
let first = x[0];
let second = x[1];
또환 인덱스를 사용해 각 요소의 값을 새로운 값으로 대체할 수 있습니다.
let x: [i32; 5] = [1, 2, 3, 4, 5];
오늘은 변수 타입에 대해 알아보았습니다.
수고 많으셨고 다음 포스팅에서 뵙겠습니다.