# Q1: Why are Box and dyn required in Vec<Box<dyn Sale>> ?

dyn — opts into dynamic dispatch

Without dyn , Rust uses static dispatch (generics/monomorphization), meaning only one concrete type per collection. dyn Sale tells the compiler: “I don’t know the exact type at compile time — look it up at runtime via a vtable.”

1
dyn Sale  // fat pointer: (ptr to data, ptr to vtable)

Box — gives the trait object a known size

dyn Sale is a Dynamically Sized Type (DST) — the compiler doesn’t know its size at compile time, so it can’t be stored directly in a Vec . Box<dyn Sale> fixes this by:

  1. Allocating each concrete value on the heap
  2. Storing a fat pointer (data ptr + vtable ptr) — always the same size regardless of the underlying type
1
2
3
4
5
Vec<Box<dyn Sale>>

├─ Box → heap: FullSale(20.0)
├─ Box → heap: OneDollarOffCoupon(20.0)
└─ Box → heap: TenPercentOffPromo(20.0)
Vec<Box<dyn Sale>> Vec<T: Sale>
Mixed types Yes No
Dispatch Runtime (vtable) Compile-time
Heap allocation Yes (Box) No

# Q2: Why do I need dyn Sale rather than just Sale ?

Before Rust 2018, you could write Box<Sale> and it implicitly meant a trait object. The dyn keyword was introduced to remove ambiguity:

Context Meaning
fn foo<T: Sale> Trait bound — static dispatch
Box<Sale> (old) Trait object — dynamic dispatch

Now dyn makes the intent explicit every time. In Rust 2021, writing Box<Sale> without dyn is a hard compile error.

1
2
3
4
fn foo<T: Sale>(s: T) { ... }     // static dispatch
fn foo(s: impl Sale) { ... } // equivalent shorthand
fn foo(s: &dyn Sale) { ... } // dynamic dispatch — must say dyn
fn foo(s: Box<dyn Sale>) { ... } // dynamic dispatch — must say dyn

# Q3: Are traits not types? Can we not store a trait directly?

Two layers:

1. A trait itself is not a type — it’s a contract.

1
let x: Sale = FullSale(20.0);   // ERROR — `Sale` is a trait, not a type

2. dyn Sale IS a type — but it’s unsized (DST).

1
let v: Vec<dyn Sale> = ...;     // ERROR — size unknown at compile time

3. A pointer layer gives it a known size.

1
2
let v: Vec<Box<dyn Sale>> = ...; // OK — Box is always pointer-sized
let x: &dyn Sale = ...; // OK — reference is always pointer-sized
Storable directly? Reason
Sale No Trait (a contract), not a type
dyn Sale No Unsized type (DST)
Box<dyn Sale> Yes Fixed-size fat pointer on heap
&dyn Sale Yes Fixed-size fat pointer (borrowed)

# Q4: Why is &'a needed in inner: Vec<&'a IdCard> ?

Rule: If a struct contains a reference, you must always annotate its lifetime explicitly. Rust never infers lifetimes in struct definitions.

1
2
3
struct YoungPeople {
inner: Vec<&IdCard>, // ERROR — every reference needs a lifetime
}

YoungPeople<'a> says: “I borrow IdCard data that lives for at least 'a .” The struct cannot outlive the data it points to.

1
2
3
4
5
6
7
let ids = new_ids();               // owns IdCard values
let young = YoungPeople {
inner: ids.inner.iter() // borrows from `ids`
.filter(|i| i.age <= 20)
.collect(),
};
// young must be dropped before ids — Rust enforces this via 'a

The returned Self in living_in_fooville is YoungPeople<'a> , meaning the new struct still borrows the original IdCard data with the same lifetime — no copying, no new ownership.


# Q5: Why does map take ownership but filter does not?

It comes down to their purpose:

  • filter only decides keep or discard — it just needs to look at the item, so it borrows ( &Item )
  • map transforms the item into something new — it needs to consume it, so it takes by value ( Item )
1
2
fn filter<P>(self, predicate: P)  // closure gets &Self::Item  (borrow)
fn map<B, F>(self, f: F) // closure gets Self::Item (by value)

In the YoungPeople example, self.inner is Vec<&'a IdCard> , so .iter() produces &&'a IdCard :

1
2
3
4
5
.filter(|i| i.city == City::Fooville)
// ^ i is &(&&'a IdCard) — just peeking

.map(|i| *i)
// ^ i is &&'a IdCard — consumed, *i dereferences to &'a IdCard
Closure receives Why
filter &Item (borrow) Only needs to inspect
map Item (by value) Needs to transform/replace

# Q6: Why is &'static str required in const MOCK_DATA: &'static str = include_str!(...) ?

Two separate reasons:

1. const always requires an explicit type annotation

1
2
3
let x = "hello";         // OK — type inferred
const X = "hello"; // ERROR — type annotation required
const X: &str = "hello"; // OK

2. include_str! produces &'static str

include_str! embeds the file directly into the binary at compile time. Since that data lives for the entire duration of the program, its lifetime is 'static .

You can also write &str — in a const context, Rust infers the 'static lifetime:

1
const MOCK_DATA: &str = include_str!("mock-data.csv");  // also valid
Requires type? Lifetime
let No (inferred) Local scope
const Yes Always 'static
static Yes Always 'static

# Q7: Why can’t Rust infer the type without Vec<_> in collect() ?

collect() can produce many different types — Vec , HashSet , HashMap , String , and more. The compiler can’t choose for you:

1
2
let data = MOCK_DATA.split('\n').skip(1).collect();
// ERROR — Do you want Vec<&str>? HashSet<&str>? Something else?

Vec<_> gives the compiler the one hint it needs:

1
2
3
let data: Vec<_> = MOCK_DATA.split('\n').skip(1).collect();
// ^^^ you say "Vec"
// ^ Rust infers "&str" from split() ✓

Three equivalent ways to write it:

1
2
3
let data: Vec<_>    = ...collect();        // you say Vec, Rust infers element
let data: Vec<&str> = ...collect(); // fully explicit
let data = ...collect::<Vec<_>>(); // turbofish syntax
Annotation Container Element
none ambiguous ❌
Vec<_> you decide Rust infers
Vec<&str> you decide you decide

# Q8: What is the difference between filter_map and map ?

map transforms every element, always producing one output per input.

filter_map transforms AND optionally drops elements in one step.

1
2
fn map<B, F: FnMut(A) -> B>(self, f: F)                // returns B
fn filter_map<B, F: FnMut(A) -> Option<B>>(self, f: F) // returns Option<B>

Example:

1
2
3
4
5
6
7
8
9
let v = vec!["1", "two", "3", "four"];

// map — keeps ALL results, including failures
let results: Vec<_> = v.iter().map(|s| s.parse::<i32>()).collect();
// [Ok(1), Err(...), Ok(3), Err(...)]

// filter_map — keeps only successful parses
let numbers: Vec<i32> = v.iter().filter_map(|s| s.parse().ok()).collect();
// [1, 3]

filter_map is equivalent to .map(f).flatten() — use it instead of .map(...).filter(|x| x.is_some()).map(|x| x.unwrap()) .

Output per input Closure returns
map Always 1 B
filter_map 0 or 1 Option<B>

# Q9: What does 'a mean in lifetime annotations?

1
2
3
fn longest<'a>(one: &'a str, two: &'a str) -> &'a str {
if two > one { two } else { one }
}

'a is a lifetime parameter — a name the compiler uses to track how long references are valid relative to each other.

  • <'a> in angle brackets declares the lifetime (like <T> declares a type parameter)
  • &'a str on each reference says they are all connected in lifetime

What 'a actually means:

It does NOT mean all references live for exactly the same duration. It means:

“The returned reference is valid for at most the shorter of the two input lifetimes.”

1
2
3
4
5
6
7
8
9
let result;
let one = String::from("long-lived");
{
let two = String::from("short-lived");
result = longest(&one, &two);
println!("{result}"); // OK — both alive here
}
// `two` dropped — `result` can't be used past this point
println!("{result}"); // ERROR — compiler catches this via 'a

Without 'a , the compiler can’t determine how long the return value lives (it could be tied to one or two ), so the function is rejected.

The name 'a is just convention — 'b , 'x , or 'lifetime all work. Single lowercase letters are idiomatic.

Edited on