Rust가 '현대적인' 프로그래밍 언어인 이유
[번역] Why Rust is a 'modern' programming language
소개
Rust는 garbage-collector가 필요 없이 메모리 안전성과 동시성을 위해 디자인된 컴파일 언어에, 정적 타입이며, 멀티 패러다임인 현대적인 프로그래밍 언어입니다.
“현대적인”이 무슨 뜻입니까?
물론, 저는 관심을 끌기 위해 현대적인 이라는 단어를 사용했습니다. 이 용어는 주관적이며 제가 “현대적인”이라고 생각하는 많은 기능은 실제로 다른 언어에서 빌린 개념입니다 (나중에 자세히 설명).
하지만, 이 언어를 진정 현대적으로 만드는 것은 의존성 관리, 빌드, 테스트 등에 대한 지원을 제공하는 엄선된 기능들과 cargo 생태계입니다.
정적 타입
논쟁의 여지가 없는 것부터 시작해 봅시다. 정적 타입이란 컴파일 시 데이터 타입을 알고 있음을 의미합니다.
Rust는 프로그램이 실행되기 전에 가능한 한 많은 문제를 찾아 컴파일 단계에서 해결하려고 합니다. 컴파일 시 데이터 타입을 알면 바로 그렇게 할 수 있습니다. 하지만 그렇다고 장황할 필요는 없습니다.
let x: u32 = 10; // 부호없는 32 비트 정수로 명시적으로 선언된 타입
let y = x + 5; // y의 타입도 u32, 하지만 컴파일러에 의해 유추됩니다.
메모리 안전성
Rust는 올바른 프로그램을 작성하는 것을 확실히 하길 원합니다. Rust는 댕글링 (잘못된) 포인터를 역 참조하지 않고 초기화되지 않은 메모리에 액세스하지 않을 것입니다 (나중에 자세히 설명). 스코프를 벗어나면 C ++의 RAII와 유사하게 메모리가 해제됩니다:
use std::fs::File; // 파일 시스템 상호 인터랙션을 담당하는 모듈에서 "import"
{
// 리소스를 확보
let fileHandler = File::open("foo.txt")?;
//... do stuff with out file
}
// fileHandler가 스코프를 벗어나면 메모리가 해제됩니다.
또한 Rust가 스코프를 벗어난 이름을 참조하도록 허용하지 않는다는 것도 좋은 점입니다.
fn add(first : u32, second : u32) -> &u32 {
let result = frist + second;
return &result; // error
}
// 여기서 result는 해제됩니다.
실행이 add
스코프를 벗어나면 result
가 할당 해제되며 (& 연산자를 통해) 참조를 반환하는 것은 유효하지 않습니다. (이 코드에서 다루지 않는 reference lifetimes 라는 것이 필요합니다.)
“현대적인”
여기가 제가 가장 좋아하는 부분입니다. 앞서 말했듯이, 이러한 많은 기능들은 다른 언어로부터 영감을 받았습니다. Rust는 많은 언어에 영향을 받아 실수에서 배우고 반복하지 않습니다. 함수형 프로그래밍에서 기능을 차용하지만 멀티 패러다임 개발을 지원하는 다음과 같은 엄선된 기능들로 빛을 발합니다.
비구조화 (Destructuring)
이것은 자바스크립트와 비슷해서 익숙할 수도 있습니다. 3D 포인트가 있다고 해봅시다, Point3D
// x,y,z 3D 축에 해당하는 3개의 부호가 없는 정수를 가진 소위 "튜플 구조체"
struct Point3D(u8,u8,u8);
비구조화를 통해 우리가 관심있는 값만 가져올 수 있습니다.
let three_d = Point3D(4,6,3);
let (x, y, _) = three_d;
println!("2D 좌표는 x: {} and y: {}", x, y);
패턴 매칭 (Pattern matching)
이 기능은 소중합니다. name
과 age
로 설명되는 Person
구조체가 있다고 가정해 보겠습니다.
struct Person {
name : String,
age : u8
}
패턴 매칭으로 기존 switch
문에 관한 항상 꿈꿔왔던 작업을 할 수 있습니다.
let p = Person { name : String::from("Jennifer"), age : 23 };
match (p.name.as_ref(), p.age) { // match 연산자 패턴은 결과를 매치하는 것이며 스위치 문과 유사하며 더 고급입니다.
("Jennifer", _) => println!("Oh, Jenn it's you, I need help with the cake, dear."),
(name, minor) if minor < 18 => println!("Dear {}, there's some RobbyBubble for you.", name),
(name, major) if major >= 18 => println!("Dear {}, there's some champain for you.", name),
_ => {} // default case
}
눈치채셨다면, 여기에도 튜플 비구조화를 사용했습니다. match 문에서 사람의 name과 age 쌍을 이룬 튜플을 만들었습니다.
패턴 매칭은 정말 강력하며 match
문 이외에 if let과 같은 것으로 응용됩니다.
기본적으로 불변성 & 섀도잉 (Immutability by default & shadowing)
Rust의 변수는 기본적으로 불변입니다. Haskell 또는 Racket과 같은 언어인 함수형 프로그래밍 의 기능처럼 상태는 변하지 않는다고 보장됩니다.
C#/Java를 예로 들자면, 이런 기능을 가지고 있지 않습니다. 기본으로 객체를 변경할 수 있는 문제는 참조 전달이 내부 상태를 엉망이 되지 않도록 해야한다는 것입니다.
불변성은 왜 갑자기 그 리스트가 비었는지? 왜 true
여야한 그 bool은 사실 false
인지? 😑 와 같은 성가신 디버깅 상황을 방지 할 수 있습니다.
이것은 보통 좋은 캡슐화로 해결되지만 항상 가능한 것은 아닙니다. 특히 서드 파티 코드를 사용할 때는 사용자가 잘 제어할 수 없습니다.
또한, Rust는 섀도잉이라는 깔끔한 기능으로 다음과 같은 작업을 수행할 수 있습니다.
// 가변 변수 명시적으로 "mut" 키워드로 표시
let mut p = Person{name: String:from(""), age : 24};
// name 읽기
println!("Please enter your name: ");
// stdin에서 읽을 가변 name 전달
std::io::stdin().read_to_string(&mut p.name);
let p = p; // "shadow" 이전 p name, 불변으로 재정의
...
마지막 구문에서, 우리는 p
의 상태를 망치는 게 아무것도 없다는 것을 압니다. 왜냐하면 거기부터 불변이기 때문입니다. ✔
No null!
제가 Rust를 “현대적”이라고 부르는 또 다른 이유로 null 참조가 없기 때문입니다. Tony Hoare는 null 참조를 그의
라고 했습니다.
Rust는 간단하게 null
이나 다른 비슷한 키워드를 가지고 있지 않습니다.
대신 optional로 값이 없음을 설명합니다 (optional type 보기). generic을 통해 이를 달성하며 예를 들자면:
let can_have_number : Option<u32> = Option::Some(6);
match can_have_number {
Some(x) => println!("We have the number: {}",x),
None => println!("There is no number to print!")
}
옵션 안에 모든 타입의 데이터를 감쌀 수 있으며, 유저는 두 가지 경우를 모두 처리해야 합니다.
데이터가 있을때, Some
안에 감싸져 있거나 데이터가 없을때. 더 잘 이해하기 위해 generic과 enum에 대해 이야기하겠습니다.
일급 열거형(Enum) 지원
enum을 사용해 본 적이 있다면 아마 C 스타일 enum에 익숙할 것입니다. 그러나 Rust에서는 어떤 심볼이나 정수 값 enum의 역할 이외에도 그것들과 관련 값을 가질 수 있습니다.
앞에서 본 Option 타입은 일반적으로 다음과 같이 정의 할 수있는 enum
입니다.
enum Option<T> {
Some(T),
None
}
Option
은 Option::Some(T)
와 Option::None
를 가집니다.
Some
은 값의 존재를 설명합니다. None
은 그것이 없음을 설명합니다. None
의 경우, 연관 값이 없기 때문에 “여기에 아무것도 없습니다.” 라고 말하는 것으로 충분합니다. 하지만 Some
은 단지 “여기에 뭔가가 있습니다.”라고 말하는 것이 아닙니다. 또한 실제로 데이터를 저장하고 제공하고 있습니다.
여러분은 이것이 패턴 매칭의 또 다른 활용 사례인지 알 수 있습니까?
특성 (Traits)
Trait는 Java / C #의 interface와 유사하며 struct가 기능을 구현하고 struct 타입을 interface 타입으로 참조할 수 있게 함으로써 다형성을 허용합니다. 그것들의 차이로 trait는 제어할 수 없는 타입에 구현될 수 있다는 점입니다.
고유 한 사진 뷰어 어플리케이션에 서드 파티 JPEG 라이브러리를 사용한다고 상상해보십시오.
이 프로그램은 이미 Photo
라는 trait을 사용하여 PNG, 비트맵 및 기타 형식을 지원합니다. 만약 JPEG 라이브러리가 그것에 대해 알고 있었다면! 😑
아마 Rust에서 당신은 라이브러리가 제공하는 JPEG 구조에 대해 Photo
trait를 구현할 수 있습니다!
더군다나 u8
, String
등과 같은 내장 Rust 데이터 타입에 대해 자체적으로 작성된 trait을 구현할 수도 있습니다. ✔
결론
대여 (borrowing), 다중 스레딩 및 많은 제로 코스트 추상화와 같은 풍부한 다른 기능들과 함께 Rust는 무료 / 오픈 소스 현대적인 언어입니다.
Mozilla 제단에서 개발된 이 프로그래밍 언어는 시스템 프로그래밍(내장된 커널 프로그래밍에서 웹 어셈블리를 통해 웹 앱에 이르기까지 모든 종류의 소프트웨어를 지원합니다.
Rust에 관심이 있으시다면 무료 Rust book을 추천합니다.