GS리테일 DX 블로그

Digital Transformation으로 고객 생활 가치의 이노베이션을 꿈꾸는 IT 사람들의 이야기

Culture

Rust 찍어먹기

pepega 2023. 7. 10. 20:50

안녕하세요, 저는 물류서비스팀 센터파트에서 백엔드 개발을 하는 고형규입니다!

회사에서 영어 이름은 민수입니다.

 

회사에서 "Rust 찍어먹기" 라는 주제로 세미나를 진행했습니다.

발표 내용을 조금 참조하여 글을 적으려고 합니다. 게임 아닙니다

 

이 글은 Rust를 영업하기 위한 글입니다.

Rust의 모든 내용을 담고있지 않고

 

Rust를 부담 없이

찍어먹는 수준의 개념과 이해를 위해 작성하였습니다 :)

 

 

목차

 

0. 간단한 소개

1. 불변성

2. Shadowing

3. Ownership

4. 참조자 (References)와 빌림 (Borrow)

5. 가변 참조자 (Mutable References)

6. 타입 추론

7. Option

8. Match

9. Result

10. 느낀점

 

 

# Rust awesome projects

메이저한 언어에 비해

아직 생태계가 미숙하다고 할 수 있지만

애플리케이션, 개발 도구, Library 같은 어썸한 프로젝트들이 있습니다.

 

그 중 Tauri VS. Electron 를 보고

Rust에 빠지게 됐습니다 :)

 

멋진 팀원 분들과 어썸한 개발 문화를 만들어서

언젠가 Rust로 빠르고 신뢰도 높은

서비스를 만들어보는게 목표입니다 :)

 

 

# Rust 로고

공식로고, https://www.rust-lang.org/

 

녹이 잘 어울릴법한 톱니바퀴 모양이 공식 로고 입니다. 

 

비공식로고, https://pbs.twimg.com/media/ELO0ClVUEAIAaWC?format=jpg&name=medium

게 (Ferris) 캐릭터는

Rust의 비공식 캐릭터입니다.

 

Rust 공식 문서의 예제 중

코드블록 오른쪽에 Ferris가 하나씩 있습니다.

 

포즈에 따라 의미가 다릅니다 :)

파일을 읽는 예제, https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html

 

게 귀여워서 할 맛이 납니다.

 

 

 

# Rust가 만들어진 계기

 

해외 커뮤니티인 Hacker News

Rust started as a personal project in 2006 라는 제목으로 글이 올라왔습니다.

 

2006년 Mozila 개발자였던 Graydon Hoare

퇴근 후 엘리베이터를 타려 했는데

엘리베이터가 고장이 났다고 합니다.

 

21층까지 걸어서 올라가야 하는 상황이 발생했습니다.

 

2006년도면

아마 엘리베이터의 소프트웨어는

대부분 C 혹은 C++ 일 것으로 예상이 됩니다.

 

그 계기로 Graydon Hoare

안전한 소프트웨어를 만들기 위해 Rust을 개인 프로젝트로 시작했다고 합니다.

 

걸어 올라가기 싫은 만큼

안전을 우선적으로 한 언어가 아닌가 싶습니다 :)

 

이제 Rust의 매력에 대해 알아볼까 합니다.

 

 

# 슈퍼어썸 컴파일 오류

 

아직 경험이 많지 않지만

이렇게 친절한 오류 메시는 처음 보았습니다.

 

어느 부분을 수정해야 하는지

어떻게 바꾸면 좋은지

 

가이드를 제공합니다.

 

따봉

https://tenor.com/ko/view/ok-all-nice-gif-22927671

 

 

# 불변성

변수를 선언하게 되면

기본적으로 불변성(immutable)으로 선언됩니다.

 

값이 할당된 변수에

다른 값을 넣을 수 없습니다.

 

Java, Rust 코드를 비교하며 보겠습니다!

 

# Java

 

java에서는 기본적으로 가변으로 선언됩니다.

위 코드는 정상적으로 실행됩니다!

 

 

# Rust

 

 

Rust는 기본적으로 변수는 불변형으로 선언됩니다.

따라서 아래와 같은 오류가 발생하게 됩니다.

 

오류 메시지를 확인해보니

mutable로 선언되어 있지 않기 때문에

`lunch_box`는 mutable로 빌릴(borrow) 수 없다고 합니다.

 

어느 부분이 not mutable인지

borrow 할 수 없는 부분이 어디인지

 

`help`로 어떻게 바꾸면 좋을지 가이드를 제공해줍니다.

 

borrow는 뒤에 추가로 설명 드리겠습니다 :)

 

가이드 대로 `lunch_box` 왼쪽에 `mut`를 붙이면

이 코드는 정상적으로 실행 될 것입니다!

 

 

 

# Shadowing

 

Rust에서는

이전에 선언한 변수와 같은 이름의 새 변수를 선언 할 수 있습니다.

이를 shadowed 됐다고 표현합니다.

 

개념은 간단합니다.

 

위 예시에서

은근슬쩍 `let`으로 변수를 할당해 주었습니다.

 

Rust에서는 `let`으로 변수 할당을 해주고 있습니다.

`let`을 한 번 더 사용해서 변수를 할당하면 shadowing를 할 수 있습니다.

 

코드를 보겠습니다!

 

`products_vector`라는 mutable vector 안에

GsPostbox라는 이름의 `struct` 2개를 push 했습니다.

 

이후 같은 이름의 `immutable`변수로 shadowing 하였습니다.

mutable -> immutable로 shadowed 되었기 때문에

 

이후 코드에서는 더 이상 `products_vector`변수 안에 push 할 수 없게 됩니다.

 

 

 

# Ownership

 

개인적으로 Rust를 즐기며 매력을 느꼈던 부분입니다.

Rust에서 가장 유니크한 특성입니다.

 

익숙하지 않은 맛 이지만

한 번 찍어 먹어보면 보면 헤어나올 수 없습니다.

 

 

우리가 서비스를 만들면

서비스가 실행되는 동안 메모리를 관리합니다.

 

우리가 익숙한 Java의 경우

더 이상 사용하지 않는 메모리를 계속 찾는

가비지 컬랙터(Garbage Collector)가 있습니다.

 

C와 비슷한 다른 언어의 경우

개발자가 직접 메모리를 할당하고 해제하는 코드를 넣어야 합니다.

말록 > 에멀록

 

사실 우리는 개발을 하면서

스택(Stack)과 힙(Heap)을 주로 생각하지 않습니다. (저만 그런 걸 수도 있습니다 ;) 

 

하지만 Rust에서는

값이 스택에 들어가는지, 힙에 들어가는지에 따라

동작 여부를 결정 짓습니다.

 

# 스택과 힙 다시 찍어먹기

일반적으로 스택은 힙에 비해서 빠릅니다!

push하고 pop하면 데이터에 접근 할 수 있으니까요

 

하지만 안전하고 빠르기 위해선

고정된 값만이 스택에 들어가야만 합니다.

 

반면에

컴파일 타임에 크기가 결정되어있지 않거나

크기가 변경 될 수 있는 데이터는

힙에 저장공간을 물어보고

해당 지점의 포인터 값으로

데이터를 저장 할 수 있습니다.

 

 

다시 Ownership으로 돌아오겠습니다.

Rust에서 Ownership은 3가지의 규칙을 가지고 있습니다.

1. Rust에서 각 값은 소유자(owner)를 갖습니다.
2. 한 번에 하나의 소유자만 있을 수 있습니다.
3. 소유자가 범위(scope)를 벗어나면 값은 드롭됩니다.

 

1. Rust에서 각 값은 소유자(owner)를 갖습니다.

변수에 값을 할당하게 되면

그 변수는 Owner 변수를 하나 가지고 있게 됩니다.

 

2. 한 번에 하나의 소유자만 있을 수 있습니다.

Owner 변수를 가진 변수를 다른 변수에 할당할 경우

Ownership은 복사되지 않고(Not copy) 이동(Move) 됩니다.

 

3. 소유자가 범위(scope)를 벗어나면 값은 드롭됩니다.

함수의 범위 `{ // superawesome code }` 를 벗어나게 되면

값은 자동으로 드롭됩니다.

 

예시 코드로 보겠습니다!

 

코드를 보다보면

번거로워 보이는 부분이 보입니다.

 

ownership 때문에 `add_vector`반환 값을 `let`으로 다시 선언해야 합니다.

 

`print_vector`함수를 호출하고 나면

`vector`parameter의 ownership이 사라지고

`ownership_is_difficult`함수에서 `fib_numbers` 변수는 ownership을 잃게 됩니다.

 

모든 함수마다 반환 값이 필요하고

함수의 반환 값을 받을 수 있는 변수를 선언해야 합니다.

 

https://tenor.com/ko/view/thinking-emoji-gif-19375822

흠.. 무언가.. 힘든 느낌입니다.

 

여기서 Rust의

참조자(References)와 빌림(Borrow)이 등장합니다.

 

 

 

# 참조자 (References)와 빌림(Borrow)

 

`&`기호가 참조자(References)를 의미합니다.

참조자는 Ownership을 넘기지 않고 참조가 가능합니다.

즉, 스코프 밖으로 벗어나도 메모리가 반납되지 않습니다!

또한 댕글림 참조자(Dangling References)가 되지 않도록 보장합니다.

 

Ownership에서 사용했던 예제를 조금 변경해보겠습니다.

 

 

변수가 기본적으로 불변인 것 처럼 참조자도 기본적으로 불변입니다.

 

기존의 `add_vector`, `print_vector`함수를

참조자를 받을 수 있게 변경했습니다.

 

`add_vector_by_reference` 함수의 parameter 또한 참조자 받도록 변경했습니다.

이것을 Rust에서는 빌림(Borrow)라고 합니다.

 

이렇게 `fib_numbers`변수를 잘 빌리고 사용하고 돌려주면서

깔끔하게 사용했습니다. :)

 

여기서 가변 참조자(Mutable References)에 대해 잠깐 언급했습니다.

Rust에서는 가변 참조자를 사용 할 때

데이터 레이스(Data race) 방지를 위해 몇 가지 규칙을 정했습니다. (규칙을 어기면 오류가 발생합니다!)

 

 

# 가변 참조자 (Mutable References)

 

데이터 레이스 조건은 아래와 같습니다.

1. 두 개 이상의 포인터가 동시에 같은 데이터에 접근한다.
2. 그 중 적어도 하나의 포인터가 데이터를 쓴다.
3. 데이터에 접근하는데 동기화를 하는 어떠한 메커니즘도 없다.

 

다른 언어에서

데이터 레이스 찾기는 너무 어려울 것 같습니다. (아직 경험해 본 적은 없지만요 ;^)

https://tenor.com/ko/view/john-travolta-well-where-is-it-looking-for-something-gif-5291661

 

 

그러나 Rust에서는

데이터 레이스가 발생할 수 있는 조건을 만들 경우

컴파일에서 오류를 발생시키게 합니다.

 

가변 참조자를 사용하여 데이터 레이스 오류가 발생되는 조건은 아래와 같습니다.

1. 한 개의 변수에 여러 개의 불변 참조자는 정상 작동합니다.
2. 한 개의 변수에 여러 개의 가변 참조자는 오류가 발생합니다.
3. 한 개의 변수에 한 개 이상의 불변 참조자와 한 개 이상의 가변 참조자가 존재하면 오류가 발생합니다.

 

1번의 경우

한 개의 변수를 여러 곳에서 읽는 것이니 문제가 생기지 않을 것 같습니다.

 

2번의 경우

한 개의 변수에 여러 가변 참조자는

충분히 문제가 생길 수 있을 것 같은 예상이 됩니다.

변수에 어떤 데이터가 들어있을지 추측하기가 어려울 것 같습니다.

 

3번의 경우

불변 참조자를 사용하는 함수는

아마 그 값이 변경 될 지 예상하지 못할 겁니다.

 

만약 값이 변경 된다면

불변 참조자를 사용하는 함수는 혼란스러울 수 있을 것 같습니다.

 

3번에 대한 예시 코드를 보겠습니다!

 

 

불변으로만 가져오는 것으로 굳게 믿고 있는  `get_woodongs`변수는

가변 참조자로 인해 값이 변경 될 경우 혼란스러울 것 같습니다.

 

이를 방지하기 위해 컴파일 레벨에서 오류가 발생하게 됩니다.

 

아래처럼 코드를 수정하면 정상 작동이 될 것 같습니다. :^)

 

 

잠깐 쉬어가는 타임으로

`let`으로 변수를 선언하며

Rust의 타입 추론에 대해 잠깐 언급하겠습니다.

 

# 타입 추론

???

타입 추론을 보여드리기 위해 코드 블록이 아닌 사진을 첨부하였습니다.

여기서 `0xff_ff_ff_ff_ff` 값을 할당하였는데

Rust는 `i32`로 자동 추론을 하고 있습니다.

 

32bit 범위는 벗어난 것 같은데..

저도 아직 모르겠습니다 ;)

아시는 분은 댓글 달아주시면 도움이 조금 많이 될 것 같습니다.

 

 

다음으로는

Rust에서 `Null`을 표현하는 방법에 대해 알아보겠습니다.

 

 

 

# Option

 

Rust는 다른 언어에서 흔히 볼 수 있는 `Null`이 존재하지 않습니다.

대신 값이 존재하는지 존재하지 않는지에 대한 열거형(enum)이 존재합니다. (익숙한 맛!)

공식 문서에는 Option이 아래처럼 구현되어 있습니다.

위 코드에 나와있는 `<T>`의 경우 Java에서도 자주 보던 제네릭 타입입니다.

 

 

`Option`을 사용하는 예시 코드를 보겠습니다.

 

 

`discount_coupon_price`변수가 `Option<u32>`으로 선언되어 있습니다.

`price`변수와 `discount_coupon_price`변수를 빼기 연산하려고 하니

오류가 발생했습니다.

 

`u32`와 `std::option::Option<u32>`는 빼기 연산을 할 수 없다는 오류가 발생하였습니다.

컴파일러는 `u32`가 항상 유효한 값을 가진다고 생각하지만

`Option<u32>`는 `None`일 수도 있기 때문에

케이스를 처리하지 않는 이상 오류가 발생합니다.

 

아직 help 부분의 오류 로그는 정확히 모르겠습니다 ;)

 

 

이 케이스를 `match`를 활용해서 해결해보겠습니다 :)

 

 

 

# match

 

다른 언어에서 `switch`와 유사한 패턴을 가지고 있습니다.

 

Rust에서의 `match`는 엄청 강력하다고 표현하고 있습니다 :)

match는 모든 가능한 경우가 들어가야 컴파일 오류가 발생하지 않습니다.

 

위에 오류가 발생했던 예시를 match로 고쳐보겠습니다!

`match`로 `match gs_postbox.discount_coupon_price`변수를 입력했습니다.

`discount_coupon_price` 변수는 `Option<u32>`로 할당되어 있으니

`Some(T)` `None`을 반드시 `match`안에 넣어야 합니다.

 

국내택배 기본 가격 : 3200
200원 할인 쿠폰, 쿠폰 할인가 : 3000
반값택배 기본 가격 : 1800
100원 할인 쿠폰, 쿠폰 할인가 : 1700
반값택배 기본 가격 : 1800
쿠폰 할인가 : 쿠폰이 적용되지 않음

 

결과가 정상적으로 출력된 것을 확인하였습니다 :)

여기서 끝이 아닙니다!

 

`match`는 아래처럼도 사용 할 수 있습니다.

`tuple`을 활용하여

`if`, `else if`, `else` 코드를 가독성 좋게 변경 할 수 있습니다.

 

여기서 `_` 코드가 나오는데요,

Rust에서는 이를 변경자(Placeholder)라고 합니다.

 

위 예시의 경우 16개의 패턴을 표현 할 수 있지만..

생각보다 번거로울 수 있습니다.

 

그 때 변경자를 사용하여

원하는 비즈니스에 알맞게 사용 할 수 있습니다!

 

 

이 글의 마지막 주제인 `Result`로 넘어가보겠습니다. :)

 

 

 

# Result

 

우리가 겪는 대부분의 에러는

전체 프로그램을 멈추도록 구현되어 있지는 않습니다.

 

오류를 예상하여 핸들링한 뒤 전달하곤 합니다.

 

Rust에서는 Result로 복구 가능한 에러를 처리합니다.

 

공식 문서에 따르면 Result는 아래와 같이 구현되어 있습니다.

`Option`과 매우 흡사해 보입니다!

 

 

간단한 예시를 들어보겠습니다.

 

시스템 OS 정보를 보여줄 때

정보를 불러오지 못하는 경우

비즈니스에 따라 오류일 수도 있고 아닐 수도 있습니다.

 

만약 오류가 아닌 경우라면

OS 정보를 호출하고

오류인 경우 다른 값을 임시로 출력해보겠습니다.

 

예시 코드를 보겠습니다!

 

 

os info : 11 (22621)

위처럼 출력되는 것을 확인 할 수 있습니다.

 

 

 

# 느낀점

 

 

# Fast and safe

아직 Rust에 찍어먹는 수준으로 학습했지만

Ownership 개념을 느끼며

빠르고 안전한 것을 중점으로 둔 언어라는 것을 느꼈습니다.

 

메모리를 문제 많이 겪었던 Microsoft는

레거시 코드를 조금씩 Rust로 변경하는 프로젝트를 진행하고 있다고 합니다.

 

# 생태계와 라이브러리

메이저한 언어들에 비해

아직 미숙한 생태계와 라이브러리를 가지고 있습니다. (신생 언어라서요)

미성숙한 생태계는 더 많은 기회를 제공하는 것 같습니다. 

기존 언어들보다 신뢰도 높고 가치가 높은 프로젝트를 만들거나 기여 할 수 있을 것 같습니다 :)

 

# 동시성에 특화되어있는 언어

Rust는 concurrent 혹은 parallel programming을 하며

발생될 수 있는 오류를 Ownership과 타입 추론으로 컴파일 오류에서 잡아낸다고 합니다. 

 

하지만.. concurrent, parallel programming은 매우매우 어려운 부분인 것 같습니다.

이를 적극적으로 활용할 수 있는 프로젝트라면

Rust을 사용할 수 있는 기회가 될 것 같습니다 :)

 

# 7년째 사랑받는 언어

Stack Overflow 설문조사에 따르면

Rust는 7년 째 사랑을 받고 있는 언어라고 합니다. (Java는 어디쯤...)

 

# 마무리

바로 프로덕션 레벨에 적용하기에는 무리가 있을 수 있을 것 같습니다.

그리고 변화가 빠른 비즈니스를 극복하는 것은 모두의 숙제일 것 같습니다.

 

상황에 대비해 러닝커브가 큰 Rust를 조금씩 학습해두는 것은

나쁘지 않은 선택이 될 것 같습니다. :^)

 

귀한 시간으로 부족한 글을 읽어주셔서 감사합니다.

이름을 적기는 조금 부끄러워서 파트 이름정도만 적겠습니다.

 

Rust를 알게 해준 물류서비스팀 배송/택배파트

세미나와 글을 작성할 수 있는 기회를 주신 물류서비스팀 센터파트와 Inno.lab 정말 감사합니다 :)

https://tenor.com/ko/view/ferris-rust-rustlang-crab-cute-gif-26396486

Rust SIG를 모집하고 있습니다!

관심이 있으신 분은 연락 주시면 감사하겠습니다 :)



 

# 참고자료

https://doc.rust-lang.org/book/

https://rinthel.github.io/rust-lang-book-ko/foreword.html

https://tenor.com 

https://news.ycombinator.com/item?id=30321747 

https://survey.stackoverflow.co/2022#section-most-loved-dreaded-and-wanted-programming-scripting-and-markup-languages

https://www.levminer.com/blog/tauri-vs-electron

 

 


고형규 Minsu | 물류서비스팀 > 센터파트

센터파트에서 백엔드 개발을 담당하고 있습니다.

호기심 많은 주니어 개발자입니다. :^)