초반 exercises에서 Rust가 for loops를 사용하여
collections를 iterate할 수 있게 해준다는 것을 배웠다.
우리는 거기서 ranges(ex. 0..5)를 봤고,
arrays나 vectors와 같은 collections도 동일하게 동작한다.
// It works for `Vec`s
let v = vec![1, 2, 3];
for n in v {
println!("{}", n);
}
// It also works for arrays
let a: [u32; 3] = [1, 2, 3];
for n in a {
println!("{}", n);
}
이제 내부적으로 이것이 어떻게 동작하는지 이해할 때다.
Rust에서 for loop를 쓸 때마다, compiler는 다음과 같은 code로 desugar한다.
(desugar: 단순화하다)
let mut iter = IntoIterator::into_iter(v);
loop {
match iter.next() {
Some(n) => {
println!("{}", n);
}
None => break,
}
}
loop는 for와 while위에 있는 또 다른 looping 구조다.
explicitly하게 break하지않는다면, loop block은 영원히 run할 것이다.
위 code의 next method는 Iterator trait으로부터 온다.
Iterator trait은 Rust의 standard library에 정의되어 있고,
values의 sequence를 생성할 수 있는 types에 대한 공유 interface를 제공한다.
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Item associated type은 iterator에 의해 생성되는 값의 type을 결정한다.
next는 sequence의 다음 value를 return한다.
만약 return할 value가 있다면, Some(value)를 return하고,
없다면, None을 return한다.
iterator가 None을 return할 때,
iterator가 exhaust된다는 보장이 없으니 조심해라.
이는 더 제한적인 FusedIterator trait을 구현하여 사용할 때, 보장된다.
모든 types가 Iterator를 구현하는 것은 아니지만,
많은 types가 Iterator를 구현하는 type으로 변환될 수 있다.
IntoIterator trait이 등장한 곳이 여기다.
trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
into_iter method는 original value를 consume하고 그것의 elements에 대한 iterator를 return한다.
하나의 type은 IntoIterator에 대한 오직 하나의 implementation을 가질 수 있는데,
이는 무엇에 대해 for가 desugar되어야하는지, ambiguity가 사라지도록 한다.
추가적으로, Iterator를 implement하는 모든 type은 IntoIterator 또한 자동으로 implement한다.
그들은 into_iter로부터 그들자신을 return한다.
iterators로 iterate하는 것은 꽤나 장점이 있다.
당신은 이와 같은 설계로 out of bounds로 갈 수 없다.
이것은 generated machine code로부터 Rust가 bounds checks를 제거하도록 하고,
iteration을 더 빠르게 한다.
다른 말로,
let v = vec![1, 2, 3];
for n in v {
println!("{}", n);
}
위 코드가 아래 코드보다 보통 빠르다.
let v = vec![1, 2, 3];
for i in 0..v.len() {
println!("{}", v[i]);
}
이 rule에는 예외사항이 있다.
compiler는 때때로 직접적인 indexing을 사용해도 out of bounds로 가지 않는 것을 증명할 수 있으므로, 어쨌든 bounds checks를 제거할 때가 있다.
그래서 일반적으로, 가능하다면 iteration을 indexing으로 선호해라.
04_iterators exercise는 IntoIterator trait을 implement하는 것이었다.
std::... path를 잘 써줘야겠다...
solution보고 이것때문이었다는 것에 허망했다.

IntoIterator는 iterator를 생성하기위해 self를 consume한다.
이것은 다음과 같은 장점이 있다.
당신은 iterator로부터 owned values를 얻는다.
예를 들어, 당신이 Vec<Ticket>에 대한 .into_iter()를 호출한다면
당신은 Ticket values를 return하는 iterator를 얻을 것이다.
하지만 다음과 같은 단점이 있다.
당신은 .into_iter()를 호출 한 후, original collection을 더이상 사용하지 못한다.
당신이 꽤 자주 consuming하면서 collection을 iterate할 일이 있다면,
이를 대신해서 values의 references를 활용해라.
Vec<ticket>의 경우, 당신은 &Ticket values을 모두 iterate하길 원할 것이다.
대부분의 collections는 .iter()이라는 method를 쓸 수 있다.
.iter() method는 collection의 elements의 모든 references를 iterator로 return한다.
예를 들어,
let numbers: Vec<u32> = vec![1, 2];
// `n` has type `&u32` here
for n in numbers.iter() {
// [...]
}
이 패턴은 collection의 reference에 대한 IntoIterator의 implement를 통해 simplify될 수 있다.
위의 우리의 예시를 보면, &Vec<Ticket>이 그 경우다.
standard library가 이것을 해주고, 다음과 같은 code가 작동하는 이유다.
let numbers: Vec<u32> = vec![1, 2];
// `n` has type `&u32` here
// We didn't have to call `.iter()` explicitly
// It was enough to use `&numbers` in the `for` loop
for n in &numbers {
// [...]
}
다음과 같은 두가지 options를 제공하는 것이 관용적이다.
- collection의 reference에 대한 IntoIterator의 구현
- collection의 elements의 모든 references를 iterator로 return하는 .iter() method
전자는 for loop에서 더 편리하고,
후자는 더 explicit하며, 다른 context에서 사용할 수 있다.
05_iter exercise는 chatgpt의 도움을 받았다.

iter function만 구현하면 되는 것이었는데,
iter method가 &self를 받아, tickets vector의 slice iterator를 반환하도록 했다.
즉, Vec에 미리 구현되어 있는 trait을 활용한 것이다.
for loop에서의 최대의 편의를 위해
&TicketStore에 IntoIterator의 implementation을 이전 exercise에 추가해보자.
implementation의 대부분의 obvious한 부분들을 채워보자.
impl IntoIterator for &TicketStore {
type Item = &Ticket;
type IntoIter = // What goes here?
fn into_iter(self) -> Self::IntoIter {
self.tickets.iter()
}
}
type IntoIter가 뭐가 돼야할까?
직관적으로, self.tickets.iter()로 return되는 type이어야한다.
(Vec::iter()로 return되는 type인)
만약 당신이 standard library documentation을 확인한다면,
당신은 Vec::iter()가 std::slice::Iter를 return한다는 것을 찾을 것이다.
Iter의 정의는 다음과 같다.
pub struct Iter<'a, T> { /* fields omitted */ }
'a는 lifetime parameter다.
Lifetimes는 mutable하든 immutable하든 얼마나 오래 reference를 valid하게 할지를 정하고,
Rust compiler가 그것을 track할 때 사용되는 labels다.
reference의 lifetime은 refer한 value의 scope로 제한된다.
Rust는 Dangling pointer와 use-after-free bug를 피하기 위해
compile time에 reference가 refer하는 value가 drop된 후에
reference가 사용되지 않도록 항상 확인한다.
이것은 꽤 친숙하다.
우리는 이미 이 개념들을 ownership과 borrowing을 얘기할 때 봤다.
Lifetimes는 특정한 reference를 얼마나 valid하게 할 지,
그저 이름짓는 방법이다.
당신이 여러 references를 갖고 있고
당신이 얼마나 그들이 서로 관련있는지 명확하게 하고 싶을 때,
naming이 중요해진다.
아래 Vec::iter()의 signature를 보자.
impl <T> Vec<T> {
// Slightly simplified
pub fn iter<'a>(&'a self) -> Iter<'a, T> {
// [...]
}
}
Vec::iter()는 'a라고 name된 lifetime parameter에 대해 generic하다.
즉, 그냥 'a라는 유효기간이 있는 것이다.
'a는 Vec의 lifetime과 iter()에 의해 return되는 Iter의 lifetime을 연결하는데 사용된다.
iter()에 의해 return된 Iter는 그것이 생성된 Vec reference(&self)보다 오래 지속될 수 없다.
이는 Vec::iter가 Vec의 elements의 모든 references를 iterator로 return하기 때문에 중요하다.
만약 Vec가 drop되면, iterator에 의해 return되는 references는 invalid하다.
Rust는 이것이 일어나지 않도록 보장해야하고, lifetimes는 이 rule을 강제하기 위한 도구다.
Rust는 lifetime elision rules라 불리는 일련의 rules가 있다.
많은 경우에 explicit lifetime annotations를 생략할 수 있도록한다.
예를 들어, Vec::iter의 정의는 std의 source code에서 다음과 같다.
impl <T> Vec<T> {
pub fn iter(&self) -> Iter<'_, T> {
// [...]
}
}
Vec::iter()의 signature에는 explicit lifetime parameter가 없다.
Elision rules는 iter()에 의해 return되는 Iter의 lifetime이 &self reference의 lifetime에 tie되어 있다고 말한다.
당신은 '_을 &self reference의 lifetime의 placeholder라고 생각할 수 있다.
대부분의 경우, 당신이 explicit lifetime annotations를 필요로할 때,
compiler가 당신에게 다 알려준다.
06_lifetimes의 exercise는 lifetime을 사용하여 내 type에 대한 IntoIterator를 implement하는 것이었다.

lifetime을 implementation 내부에만 설정했더니 오류가 발생했다.
당연히 lifetime parameter를 주지 않아서 생긴 문제였다.
그래서 implementation signature에 lifetime parameter를 줬고,
그렇게 해결할 수 있었다.
chatgpt를 사용하여 공부하는 것이 효율적인 것 같다.
GPT가 Rust를 잘하는 것 같다.
'Rust Programming' 카테고리의 다른 글
| [Rust] 6. Ticket Management: Slices, Mutable slices, Two states (0) | 2024.10.02 |
|---|---|
| [Rust] 6. Ticket Management: Combinators, impl Trait (0) | 2024.08.09 |
| [Rust] 6. Ticket Management: Arrays, Vectors, Resizing (0) | 2024.08.07 |
| [Rust] 5. Ticket v2: TryFrom trait, Error::source (0) | 2024.08.06 |
| [Rust] 5. Ticket v2: Packages, Dependencies, thiserror (0) | 2024.08.05 |