Rust 설치 및 환경 설정 구성

zdpk·2024년 6월 24일

Rust

목록 보기
2/4
post-thumbnail

https://velog.io/@mainfn/rust1

이전 장에서 Rust의 메모리 관리 방식에 대해 소개했었다.

이번 장에서는 간단하게 설치 및 환경 설정을 구성해보도록 하겠다.

환경 설정이 귀찮은 사람들을 위해 그런 것이 필요 없는 대안들을 몇가지 준비해두었다.

1. Rust Playground

https://play.rust-lang.org/

Pros

  • 아무런 절차가 없으며 가장 간편

Cons

  • AutoCompletion 불가
  • 프로젝트 단위로 저장이 안 됨

2. Google IDX

Pros

  • AutoCompletion 및 VSCode extension 그대로 설치 가능
  • 프로젝트 단위로 저장 가능

Cons

  • 구글 로그인 필요
  • Workspace Setup 시간 느림

그 외에도 여러 대안들이 있으나 글이 너무 길어질 것 같으니 마음에 드는 것을 찾아서 사용하도록 하자.

https://replit.com/languages/rust

https://www.programiz.com/rust/online-compiler/

https://www.onlinegdb.com/online_rust_compiler

https://www.mycompiler.io/new/rust

https://onecompiler.com/rust

https://codeanywhere.com/languages/rust

https://www.jdoodle.com/execute-rust-online


Installation

macOS면 brew install rust로도 설치가 가능하다.

그러나 웬만하면 공식 문서에서 추천하는 아래 명령어로 설치하도록 하자.

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

나중에 명령어를 잊어버리면 아래 두 링크 중 아무거나 참조하면 된다.

https://rustup.rs
https://www.rust-lang.org/tools/install

위 명령어는 rustup이라는 toolchain을 설치하는데, cargo, rustc, rustfmt 등을 자동으로 설치 및 관리할 수 있고, stable, nightly 등의 채널도 관리할 수 있으며 깔끔하게 HOME 디렉토리에 설치된다.

# Home directory로 이동
$ cd $HOME

---

# .rustup 찾기
$ ls -alsf | grep rustup
0 drwxr-xr-x@    7 x     staff      224 Jun  9  2023 .rustup

---

# .cargo 찾기
$ ls -alsf | grep cargo
0 drwxr-xr-x@   12 x     staff      384 Jun 24 21:05 .cargo

.rustup, .cargo 디렉토리가 잘 설치된 것을 확인했다.

설치는 이게 끝이다.

매우 간단하다.

rustupcargo가 뭐하는 도구인지 좀 더 자세히 살펴보자.


rustup

https://rust-lang.github.io/rustup

rustup은 toolchain이다.

소프트웨어를 개발하는 데 필요한 툴셋이라고 보면 되며,

rustc, cargo, rustfmt, clippy, rust-std 등을 포함하고 있다.

다음 경로로 이동해서 stable~ 로 시작하는 디렉토리로 이동해보자.

$ cd ~/.rustup/toolchains

나는 M1 macOS를 사용하고 있기 때문에 stable-aarch64-apple-darwin이라는 폴더가 만들어져 있었다.

CPU Architecture, OS에 따라 폴더명이 달라질 것이기 때문에 직접 ls로 찍어봐야 한다.

stable-aarch64-apple-darwin이라는 이름은 Target Triple이라는 표기법에 의해 만들어지는데, 내용이 길어지므로 무슨 의미인지 궁금하면 아래 링크를 참고하자.

https://velog.io/@mainfn/wasm32-unknown-unknown

각자의 플랫폼에 맞게 이동한 디렉토리 밑에 bin 디렉토리가 있을 것이다.

내 경우는 ~/.rustup/toolchains/stable-aarch64-apple-darwin/bin다.

여기서 ls로 어떤 명령어들이 있는지 살펴보자.

$ ls -alsf

total 186392
    0 drwxr-xr-x  13 x  staff       416 Feb 19 02:39 .
    0 drwxr-xr-x@  7 x  staff       224 Feb 15 03:10 ..
20224 -rwxr-xr-x   1 x  staff  10352833 Feb 15 03:10 rustdoc
57896 -rwxr-xr-x   1 x  staff  29642691 Feb 15 03:10 cargo
68976 -rwxr-xr-x@  1 x  staff  35313867 Feb 19 02:39 rust-analyzer
20352 -rwxr-xr-x   1 x  staff  10418859 Feb 15 03:10 clippy-driver
    8 -rwxr-xr-x   1 x  staff      2160 Feb 15 03:10 rust-gdbgui
    8 -rwxr-xr-x   1 x  staff      1072 Feb 15 03:10 rust-lldb
 1816 -rwxr-xr-x   1 x  staff    927226 Feb 15 03:10 cargo-clippy
 1256 -rwxr-xr-x   1 x  staff    643048 Feb 15 03:10 rustc
 2960 -rwxr-xr-x   1 x  staff   1512679 Feb 15 03:10 cargo-fmt
12888 -rwxr-xr-x   1 x  staff   6595461 Feb 15 03:10 rustfmt
    8 -rwxr-xr-x   1 x  staff       980 Feb 15 03:10 rust-gdb

아까 언급했던 cargo, rustc 등이 존재한다.

rustup을 설치하면 딸려오는 것들이며, Rust 개발에 반드시 필요한 핵심 도구들이라 볼 수 있다.


cargo

cargopackage manager이자 build tool이다.

npm(node), pip(python), maven(java), nuget(C#) 등과 비슷하다.

다양한 명령어들이 존재하는데, 우선 cargo new로 간단한 프로젝트를 하나 생성해보자.

$ cargo new <project>

이름은 원하는대로 짓고, 원하는 에디터로 프로젝트를 열어보겠다.

웬만하면 무료인 VSCode를 추천한다.

아직 Jetbrains의 RustRover는 시험작 느낌이 강하고 무거우며,

Helix는 Plugin System이 없고 Neovim은 세팅이 불편하다.

Zed도 아직 기능이 부실하기 때문에 24년 6월 현재는 VSCode 만한 것이 없는 것 같다.

이건 나중에 바뀔 수도 있다.

어쨌든 대다수가 VSCode를 사용할 것이라고 생각하는데, 이거 하나 깔아주면 된다.

LSP(Language Protocol Server)

LSP 기반이기 때문에 VSCode 뿐만 아니라 다양한 에디터와 호환된다.

LSP를 대략적으로 설명하면, 프로그래밍을 할 때 Auto Complete, Go to Definition, Refactoring 등의 다양한 고급 편집 기능이 요구된다.

예를 들어 함수나 매크로 위에 커서를 갖다 대면 문서를 보여준다던가,

타입을 명시하지 않아도 추론해서 보여준다던가

경고나 오류를 띄워준다던가

하는 기능들을 LSP가 지원한다.

이런 기능들이 없으면 메모장이고 조금 있으면 에디터, 강력하면 IDE가 된다.

Rust Analyzer는 LSP 기반의 Analyzer다.

Rust Analyzer는 에디터를 IDE 수준으로 탈바꿈 시켜주는 매우 강력한 필수 요소다.

Rust Analyzer가 LSP 기반인 이유를 좀 더 자세히 살펴보자.

개발자는 실시간으로 코드를 타이핑 할 것이고, 이를 끊임없이 분석해서 오류 및 경고를 찾아내고, 검색을 수행하고 다양한 부가 정보, 기능 등을 제공해야 하는데,

이러한 기능을 모든 에디터가 각자 쌩까고 구현하면 호환성이 전혀 없게 된다.

모든 에디터가 각자 기능을 다시 구현해야 하는 바퀴의 재발명 현상이 곳곳에서 일어날 것이다.

또한 프로그래밍 언어도 수 천개가 넘는데 모든 프로그래밍 언어가 거의 비슷한 기능을 가지고 있음에도 불구하고 기능을 다 다른 방식으로 구현하면 난장판이 될 것이다.

언어가 1,000개면 기능 하나 변경하면 에디터마다 1,000번 바꿔야 한다.

그래서 에디터건 언어건 상관 없이 표준 프로토콜 하나 만들어서 여기에 맞춰서 구현하자고 한게 LSP고

MS에서 만들었다.

RLS, RA는 Rust를 위한 LSP 구현체인 것이다.

비슷한 예로 Java 진영에서는 JVM 스펙이 LSP 프로토콜, 이를 구현한 OpenJDK, Azul, Amazon corretto 등이 RLS, RA 등의 LSP 구현체와 비슷하다고 볼 수 있다.

JS에서는 ECMAScript가 명세고, 이에 맞춰 구현된 JS Engine들. V8, SpiderMonkey 등이 구현체라 볼 수 있겠다.

원래 RLS(Rust Language Server)라는 Rust 공식 Analyzer가 있었지만, Rust Analyzer가 훨씬 잘 만들어서 사람들이 다 이거를 사용하니까 공식 도구로 편입해 버리고 RLS는 deprecated 되었다.

혹시 옛날 글 같은걸 보다가 RLS를 사용하는 것을 봐도 망했으므로 앞으로는 RA만 사용하면 된다.

(Rust Analyzer는 줄여서 RA라고도 많이 부르니 기억해두자.)

어쨌든 이러한 LSP 표준을 제정한 덕분에 Zed, Helix 등 새로운 에디터가 나오더라도 Rust Analyzer를 사용하면 비슷한 수준의 경험을 맛볼 수 있다는 장점이 있다.(그러나 Extension은 예외다..)


Hello World

src/main.rs 파일이 만들어져 있을 것이다.

Run, Debug 버튼이 예쁘게 main 함수 옆에 만들어져 있다.

이것 또한 우리의 RA 덕분이다.

버튼을 눌러서 실행해보자.

$ cargo run

또한 위 명령어로도 실행이 가능하다.

Run 버튼을 누르면 저 명령어를 대신 수행해준다고 보면 된다.

이번에는 crate를 설치해보자.

# crate 설치
$ cargo add <dependency>

cargo add randrand crate를 설치할 것이다.

참고로 Rust는 crate가 배포단위다. JAR, npm package과 비슷하다고 생각하면 된다.

npm처럼 명령어 한 줄로 간단하게 패키지 설치가 가능하다.

Cargo.toml 파일의 [dependencies]에 설치된 패키지가 기재된다.

# Cargo.toml
[package]
name = "main"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"

package.json이라 생각하면 된다.

이제rand를 사용해보자.

fn main() {
    let n = rand::random::<i32>();
    println!("Hello, world! {n}");
}

rand::random은 랜덤값을 생성해주는 함수다.

문자열에서 "{}" 사이에 변수를 넣어주면 출력된다.

문법은 신경쓰지 말고 다음 명령어로 실행해보자.

cargo run

Hello World와 함께 랜덤 숫자가 출력될 것이다.


crates.io

npm registry, maven registry와 비슷한 Rust crate를 다운로드 받을 수 있는 중앙 저장소다.

https://crates.io/

crate를 검색하는 데는 lib.rs가 더 편할 수도 있으니 참고 바란다.

https://lib.rs


cargo build

다음 명령어로 실제 바이너리 파일이 손쉽게 build된다.

$ cargo build

프로젝트 루트에서 target/debug/<project> 경로로 바이너리 파일이 생성되었다.

나는 project 이름을 main으로 설정하였으므로, 바이너리 파일명은 main이 되었다.

다음 명령어로 빌드된 바이너리를 실행할 수 있다.

$ ./target/debug/main

이전과 동일한 결과가 나온다.


rustc를 통한 build

cargo build를 통해 손쉽게 executable file(바이너리 실행 파일)을 만들 수 있었다.

아까 rustup을 설치할 때, rustc가 함께 설치되었던 것이 기억 나는가.

rustc가 바로 Rust Compiler다.

cargo build도 내부적으로 rustc를 사용한다.

그럼 무슨 차이냐 하면, cargo build는 프로젝트의 의존성을 자동으로 관리하고 연결해준다.

고로 우리가 build하는 행위 자체를 매우 편하게 한 줄의 명령어로 수행될 수 있게 해준다.

그러나 이런 편리함을 너무 당연하게 생각하고 있다면, cargo의 고마움을 느끼지 못할 것이기 때문에 한 번 rustc만 사용하여 깡으로 빌드 해보겠다.

$ rustc src/main.rs -o main

src/main.rs 파일을 컴파일해서 output으로 main이라는 실행 파일을 만들라는 의미다.
(-o를 생략해도 자동으로 main으로 만들어주기는 하지만 명시했다.)

다음 순간 오류가 발생한다.

error[E0433]: failed to resolve: use of undeclared crate or module `rand`
 --> src/main.rs:2:13
  |
2 |     let n = rand::random::<i32>();
  |             ^^^^ use of undeclared crate or module `rand`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0433`.

rand crate를 찾을 수 없다고 한다.

cargo를 사용하지 않고, rustc로 컴파일을 하게 되면 개발자가 직접 의존성을 모조리 수동으로 빌드하고 적절한 디렉토리에 배치, 및 관리하면서 컴파일 시에 경로를 명시해야 한다.

cargo가 없다고 가정할 것이므로, 당연히 설치도 수동으로 해야한다.

의존성을 설치할 디렉토리 deps를 생성하고 그 안에서 rand 소스 코드를 git clone으로 수동 설치하겠다.

# 의존성을 수동으로 설치, 관리할 디렉토리 생성
$ mkdir deps && cd deps

# rand crate의 소스 코드 다운로드
$ git clone https://github.com/rust-random/rand.git

그리고 ./deps/rand로 이동하여 직접 빌드한다.

# rand 디렉토리로 이동
$ cd ./rand

# rand crate를 수동 빌드
$ rustc --crate-type=lib src/lib.rs

rustc --crate-type=lib src/lib.rs를 실행하면 다수의 오류가 발생한다.

error[E0432]: unresolved import `rand_core`
  --> src/lib.rs:97:9
   |
97 | pub use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore};
   |         ^^^^^^^^^ maybe a missing crate `rand_core`?
   |
   = help: consider adding `extern crate rand_core` to use the `rand_core` crate

error[E0433]: failed to resolve: maybe a missing crate `rand_core`?
  --> src/rngs/reseeding.rs:15:5
   |
15 | use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng};
   |     ^^^^^^^^^ maybe a missing crate `rand_core`?
   |
   = help: consider adding `extern crate rand_core` to use the `rand_core` crate

이유는 rand 디렉토리의 Cargo.toml을 보면 알 수 있다.


[dependencies]
rand_core = { path = "rand_core", version = "=0.9.0-alpha.1", default-features = false }
log = { version = "0.4.4", optional = true }
serde = { version = "1.0.103", features = ["derive"], optional = true }
rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.1", default-features = false, optional = true }
zerocopy = { version = "0.7.33", default-features = false, features = ["simd"] }

[dev-dependencies]
rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" }
# Only to test serde1
bincode = "1.2.1"
rayon = "1.7"

rand 또한 의존성을 가지고 있기 때문에, 이 의존성 또한 각각 컴파일 해야 하는 것이다.

그런데 저 의존성들도 또한 의존성이 존재한다.

cargo가 없다면 의존성과 의존성의 의존성을 모두 수동으로 빌드해야 하고, 이는 생각보다 훨씬 어렵고 고된 작업이다.

C++의 빌드가 너무 복잡하고 어렵다는 말은 cargo와 같은 표준화 된 빌드 및 패키지 관리 시스템이 존재하지 않는다는 부분이 큰 지분을 차지하는 것 같다.

C++을 잘 알지는 못하지만, C++은 이 외에도 플랫폼, 컴파일러에 따라 달라지는 부분들도 많고, 전처리 단계도 존재하기 때문에 얼마나 복잡할지 상상도 되지 않는다.

만약 rand를 직접 빌드한다면, 의존성을 전부 git clone해서 설치하고 빌드하고, 만약 오류가 난다면 그 의존성의 의존성부터 설치하는 작업이 필요할 것이다.

또한 잘 보면 의존성 뿐만 아니라 crate 안의 crate도 존재하는데, 이를 처리하기 위해서는 아래와 같은 과정이 계속해서 반복될 것이다.

# rand_core 컴파일
cd rand_core
rustc --crate-type=lib src/lib.rs
cd ..

# getrandom 컴파일
cd getrandom
rustc --crate-type=lib src/lib.rs -L ../rand_core --extern rand_core=../rand_core/librand_core.rlib
cd ..

# rand_chacha 컴파일
cd rand_chacha
rustc --crate-type=lib src/lib.rs -L ../rand_core --extern rand_core=../rand_core/librand_core.rlib
cd ..

# rand 컴파일
cd rand
rustc --crate-type=lib src/lib.rs -L ../rand_core -L ../getrandom -L ../rand_chacha --extern rand_core=../rand_core/librand_core.rlib --extern getrandom=../getrandom/libgetrandom.rlib --extern rand_chacha=../rand_chacha/librand_chacha.rlib

# ...

그 후에 앞서 언급한 의존성을 설치하고 빌드하는 작업이 들어가야 한다.

이러한 고통스러운 과정을 직접 겪고 싶진 않기 때문에 rand crate는 배제하고, 직접 crate를 만들어보겠다.


lib crate

crate는 2가지 종류로, binary crate, lib crate가 존재한다.

binary cratemain.rs를 포함한 Entry Point가 있는 crate다.

lib crate는 Entry Point가 되는 것이 아니라, 다른 crate들에 의해 사용되는, 말 그대로 라이브러리로 사용되는 crate를 뜻한다.

우리는 lib crate를 만들어서 컴파일 한 뒤, 이를 이전에 만든 binary crate에서 의존하도록 만든 다음 rustc로 컴파일 할 것이다.

# lib crate 생성
$ cargo new --lib my_rand

lib crate를 만들 때는 --lib 옵션을 붙여줘야 한다.

이름은 my_rand로 하였다.

디렉토리 구조는 동일하지만, main.rs 대신 lib.rs가 존재한다.

lib.rs를 원래 있던 내용을 지우고 다음 코드로 대체하겠다.

// my_rand/src/lib.rs
pub fn rand() -> i32 {
    10
}

항상 10을 반환하는 단순한 함수다.

이제 원래 프로젝트의 Cargo.toml에서 의존성도 변경해주자.

[package]
name = "main"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

# ✅ 추가
[workspace]  
 members = ["my_rand"] 

[dependencies]
# ❌ 제거
# rand = "0.8.5"
# ✅ 추가
my_rand = { path = "./my_rand" }

main.rsmy_rand를 호출하도록 변경했다.

fn main() {
    let n = my_rand::rand();
    println!("Hello, world! {n}");
}

이제 my_rand를 먼저 컴파일 하고, main을 컴파일 된 my_rand와 함께 묶어서 컴파일 하면 된다.

# my_rand로 이동
$ cd my_rand
# my_rand를 my_rand.rlib로 컴파일
$ rustc --crate-type=lib src/lib.rs -o my_rand.rlib

이 때, lib crate이므로 lib 타입으로 컴파일 해야 한다.

또한 my_rand.rlib라는 이름으로 파일명을 지정했는데, rlibRust Library의 약자로, 바이너리가 되기 이전 단계인 IR(중간 코드) 상태다.

# 다시 project root로 이동
$ cd ..

# 이전에 컴파일 한 my_rand/my_rand.rlib와 함께 컴파일
$ rustc src/main.rs --extern my_rand=my_rand/my_rand.rlib 

그러나 이번에도 역시 오류가 난다.

error: extern location for my_rand is of an unknown type: my_rand/my_rand.rlib
 --> src/main.rs:3:13
  |
3 |     let n = my_rand::rand();
  |             ^^^^^^^

error: file name should be lib*.rlib or lib*.dylib
 --> src/main.rs:3:13

error: file name should be lib.rlib or lib.dylib

rlib 파일의 경우, 파일명이 lib로 시작해야 한다는 컨벤션 때문이다.

아까 컴파일 한 파일명을 my_rand.rlib에서 libmy_rand.rlib로 변경하고 다시 실행해보자.

드디어 정상적으로 컴파일 되었다.

$ ./main

Hello, world! 10

이제 실행까지 완벽히 되는 것을 확인할 수 있다.

여기까지 왔다면 cargo 없는 의존성 관리, 빌드가 얼마나 어려운 것인지 체감했을 것이라고 생각한다.

고로 rustc는 내부 동작만 이해하고, 웬만하면 직접 사용하지 말자


alias

cargo가 조금 길다면 ~/.zshrcalias로 등록하자.

위처럼 자주 사용되는 alias는 확실히 단어 하나로 축약해 두는 것이 편리한 것 같다.


기본적인 Rust 환경을 구성하고 cargo의 기초적인 사용법에 대해서 알아보았다.

Rust의 핵심 컨셉과 문법적인 요소는 다음 장부터 본격적으로 살펴보면 되겠다.

0개의 댓글