비욘드 JS: 러스트 -변수와 타입

dante Yoon·2022년 12월 17일
1

beyond js

목록 보기
1/20
post-thumbnail

동영상 강의 보기

글을 쓰기에 앞서서

안녕하세요, 단테입니다.

2022년 한 해동안 러스트에 대한 관심이 국내외로 뜨거웠습니다.

러스트는 커뮤니티가 활발하고 공식문서가 잘 정리되어 있기 때문에 새로운 개발자가 쉽게 접근할 수 있습니다. 정적 타입 시스템과 메모리 안정성을 제공하기 때문에 안정적인 코드를 작성할 수 있으며 컴파일 시간이 빠르고 코드의 성능을 극대화 할 수 있다는 프로그래밍 언어입니다.

러스트 언어를 활용해 만들 수 있는 분야는 다양합니다.

웹 어셈브리를 사용한다면 JS를 사용하지 않고도 브라우저 상에서 작동하는 앱을 오직 러스트로만 작성할 수 있으며, JS 보다 높은 성능 높은 앱을 만들 수 있습니다. 피그마 또한 러스트가 활용되었으며

SWC, STC등 러스트의 성능상 이점을 이용한 개발을 위한 툴들도 많이 제작되고 있습니다.

JS/TS에서 더 나아가 더 나은 개발자로 성장하기 위해 금년도부터 러스트 공부를 시작하겠습니다.

비욘드 JS: 러스트 시리즈에서 다루는 러스트 학습 내용은 무료 영문 자료인 The Rust Programming Language에서 가져왔습니다.

Variables and Mutability

Variable

러스트의 변수는 mut 키워드로 선언하지 않으면 기본적으로 immutable입니다.

여기서 immutability란 JS에서 ES6 부터 등장한 let , const 키워드를 사용해 설명하자면,
let을 통해 선언한 변수는 mutable , const를 통해 선언한 변수는 immutable입니다.

Carlos Cuesta - JS immutability

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

Constant

러스트에서는 variable과 constant가 엄격하게 분류되어있습니다.
immutable variable과 마찬가지로 한번 선언된 이후에는 변경이 불가능한데요, let varconst var은 어떤 차이점이 있을까요?

constants에는 mut 키워드를 사용할 수 없습니다.

constants 선언 시 타입은 꼭 명시되어야 합니다.

러스트는 정적 타입 언어입니다.
코드 작성 시 컴파일러에서 타입을 추론할 수 있는데요,
variable 선언 시 러스트 컴파일러가 자동으로 타입을 추론하는 것과 다르게 constants 사용 시 명시적으로 타입을 선언해주어야 합니다.

TS의 const 사용과 다른 점입니다.

// rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

node.js 환경에서 constants.ts와 같은 파일에 모든 상수를 선언해두고 앱 전체에서 불러 사용하는 것과 동일하게 러스트의 const 또한 선언된 스코프 내부에서는 어디에서도 사용할 수 있습니다.

Shadowing

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 키워드를 사용하는 것은 다음과 같은 차이점이 있습니다.

변수의 immutability와 타입을 보존 합니다.

아래 코드는 쉐도잉을 통해 두번째 선언된 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라는 변수명을 재사용할 수 있다는 장점이 있습니다.

Data Types

이제 자료형에 대해 알아보겠습니다.

자바스크립트와는 어떤 차이점이 있는지 살펴봅시다.

러스트에서 사용되는 모든 값은 특정 타입을 가지고 있습니다.
러스트는 정적 타입 언어이기 때문에 컴파일 타임에 값의 타입을 파악할 수 있습니다.

러스트 컴파일러는 많은 경우 타입스크립트 컴파일러와 동일하게 타입을 명시적으로 선언하지 않아도 추론하 수 있지만 꼭 명시적으로 작성해주어야 하는 경우도 있습니다.

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

Scalar Types

스칼라 타입은 자바스크립트의 Primitive Types와 유사합니다.

Integer

정수 타입은 표현하는 숫자 크기에 따라 타입 지정이 다릅니다.
자바스크립트와 두드러지는 차이점인데요, uunsigned, isigned를 의미합니다.

  • unsigned: 양수만
  • 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를 사용하는 것이 가장 일반적인 방법입니다.

Floating-Point

러스트는 자바스크립트와 다르게 부동소수점 형 숫자 타입을 별도로 가지고 있습니다.

fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}

Numeric Operations

연산 표기는 자바스크립트와 다를게 없습니다.

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;
}

Boolean

러스트에서 참/거짓을 나타내는 불린 타입은 1바이트의 크기를 가지고 있습니다.

fn main() {
    let t = true;

    let f: bool = false; // with explicit type annotation
}

Character

자바스크립트에서는 문자열 타입을 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 Types

러스트의 Compound Type은 튜플과 배열이 있습니다.

Tuple

자바스크립트에서는 튜플 타입이 없지만 러스트에서는 지원하며, 타입스크립트에서 배열을 튜플형식으로 사용하는 것과 다르게 러스트에서는 별도의 타입으로 존재합니다.

튜플은 여러 개의 타입으로 이뤄질 수 있는데요, 참조하는 문법이 배열과 상이합니다.
자바스크립트의 배열과 다르게 [] 가 아닌 ()를 통해 선언되었습니다.

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;
}

Array

러스트의 배열은 자바스크립트의 배열과는 다소 다릅니다. 맨 처음 선언된 길이 만큼의 요소만 가질 수 있으며 모든 요소의 타입은 동일해야 합니다.

예를 들어 정수 5개를 갖는 array 타입은 다음과 같이 표기할 수 있습니다.

let x: [i32; 5] = [1, 2, 3, 4, 5];

생소한 타입 선언이 나왔습니다. i32 타입을 5개 만큼 가지는 변수이기 때문에
[i32; 5]라고 선언했습니다.

러스트의 array 타입은 [T; N] 형식으로 작성되며, T는 요소의 타입을, N은 요소의 개수를 의미합니다. array 타입은 상수 값을 저장하는 데 적합합니다.

Accessing Array Elements

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];

글을 마치며

오늘은 변수 타입에 대해 알아보았습니다.
수고 많으셨고 다음 포스팅에서 뵙겠습니다.

profile
성장을 향한 작은 몸부림의 흔적들

0개의 댓글