테스트 케이스: 연결 리스트
통상적으로 연결 리스트를 구현하는 방법은 enum을 활용하는 것입니다:
// 역주: 이렇게 하면 해당 파일 전체에서 `List` 하위 객체를
// 스코핑 없이 바로 접근할 수 있게 됩니다.
use crate::List::*;
enum List {
// Cons: 항목과 다음 노드로의 포인터를 감싸는 튜플 구조체
Cons(u32, Box<List>),
// Nil: 연결 리스트의 끝을 나타내는 선언자
Nil,
}
// 열거형도 메서드를 붙일 수 있습니다
impl List {
// 빈 `List`를 생성합니다
fn new() -> List {
// `Nil`은 `List` 타입입니다
Nil
}
// `List`의 소유권을 가져와, 새 항목이 맨 앞에 추가된 `List`를
// 만들어 리턴합니다.
fn prepend(self, elem: u32) -> List {
// `Cons`도 `List` 타입입니다
Cons(elem, Box::new(self))
}
// `List`의 길이를 리턴합니다
fn len(&self) -> u32 {
// `self`의 종류에 따라 행동이 바뀌기에 매칭이 필요합니다.
// `self`는 `&List` 타입이고 `*self`는 `List` 타입이며,
// 정적 타입 `T`가 `&T`보다 매칭에 선호됩니다.
// Rust 2018부터는 레퍼런스 없이 self나 tail을 사용할 수 있으며,
// 이때 러스트는 `&s`와 `ref tail`을 추정해 컴파일합니다.
// 더 보기: https://doc.rust-lang.org/edition-guide/rust-2018/ownership-and-lifetimes/default-match-bindings.html
match *self {
// `self`가 빌려온 것으로 `tail`을 가져올 수 없어, 이의 레퍼런스를
// 가져옵니다.
Cons(_, ref tail) => 1 + tail.len(),
// 빈 리스트는 기본적으로 0의 길이이죠.
Nil => 0
}
}
// 리스트 전체를 (heap 할당된) `String`으로 리턴합니다
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
// `format!`은 `print!`와 유사하지만, 콘솔에 뿌리는 대신
// heap 할당된 string을 리턴합니다
format!("{}, {}", head, tail.stringify())
},
Nil => {
format!("Nil")
},
}
}
}
fn main() {
// 빈 연결 리스트를 만듭니다
let mut list = List::new();
// 서두에 아이템 몇 개를 추가합니다
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);
// 최종 상태를 확인합니다
println!("linked list has length: {}", list.len());
println!("{}", list.stringify());
}