테스트케이스: 연결 리스트
연결 리스트를 구현하는 일반적인 방법은 enums를 사용하는 것입니다:
use crate::List::*;
enum List {
// Cons: 요소와 다음 노드에 대한 포인터를 감싸는 튜플 구조체
Cons(u32, Box<List>),
// Nil: 연결 리스트의 끝을 나타내는 노드
Nil,
}
// 열거형에 메서드를 붙일 수 있습니다
impl List {
// 빈 리스트를 생성합니다
fn new() -> List {
// `Nil`은 `List` 타입을 가집니다
Nil
}
// 리스트를 소비하고, 그 앞에 새로운 요소가 추가된 동일한 리스트를 반환합니다
fn prepend(self, elem: u32) -> List {
// `Cons` 역시 `List` 타입을 가집니다
Cons(elem, Box::new(self))
}
// 리스트의 길이를 반환합니다
fn len(&self) -> u32 {
// 메서드의 동작이 `self`의 변체에 따라 달라지므로 `self`에 대해 매치(match)를 수행해야 합니다.
// `self`는 `&List` 타입이고, `*self`는 `List` 타입입니다. 구체적인 타입 `T`에 대한 매칭이
// 참조 `&T`에 대한 매칭보다 선호됩니다.
// Rust 2018 이후에는 여기서 self를 사용하고 아래에서 tail(ref 없이)을 사용할 수도 있습니다.
// Rust가 &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(),
// 기저 사례(Base Case): 빈 리스트의 길이는 0입니다
Nil => 0
}
}
// 리스트를 (힙에 할당된) 문자열 표현으로 반환합니다
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
// `format!`은 `print!`와 유사하지만, 콘솔에 출력하는 대신
// 힙에 할당된 문자열을 반환합니다
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!("연결 리스트의 길이는 {}입니다", list.len());
println!("{}", list.stringify());
}