FAQ
Quam et rendaerum aut ea niscit veriae sit aut anderit aturiate prae sit a no eium labo. Neque dis explibus di blaccuptatem re alitlis cimaxima dioris.
How does Rust's variance system work with lifetimes?
Variance determines how subtyping works with generic types. Rust has covariance (T can be substituted with a subtype), contravariance (T can be substituted with a supertype), and invariance (no substitution allowed). For example, &'a T is covariant over both 'a and T, while &'a mut T is covariant over 'a but invariant over T to prevent soundness issues.
What are Higher-Ranked Trait Bounds (HRTBs) and when do you need them?
HRTBs, written as 'for<'a>', allow you to express that a trait bound must hold for all possible lifetimes. They're essential when working with closures that accept references, like in Iterator::map. For example, 'F: for<'a> Fn(&'a T)' means F must work with any lifetime 'a, not just one specific lifetime.
How does the Send and Sync auto-traits system ensure thread safety?
Send indicates a type can be transferred across thread boundaries, while Sync means multiple threads can safely hold references to it. These are auto-traits - the compiler implements them automatically unless you have non-thread-safe components like raw pointers or Rc. Understanding when to impl !Send prevents data races at compile time.
What is monomorphization and how does it affect compile times and performance?
Monomorphization is Rust's strategy for generics where the compiler generates specialized code for each concrete type used. This gives zero-cost abstractions and excellent runtime performance, but increases compile time and binary size. It's why Vec<i32> and Vec<String> are completely separate types in the compiled binary.
How do trait objects and dynamic dispatch differ from static dispatch?
Trait objects (dyn Trait) use vtables for dynamic dispatch, determining which method to call at runtime. This enables heterogeneous collections and runtime polymorphism but has a small performance cost. Static dispatch with generics is faster but requires knowing types at compile time and can lead to code bloat through monomorphization.
What are the implications of Rust's coherence rules for trait implementations?
The orphan rule prevents trait conflicts: you can only implement a trait for a type if either the trait or type is local to your crate. This ensures that adding dependencies never breaks existing code. The newtype pattern (struct Wrapper(T)) is used to work around this limitation when you need to implement external traits for external types.
How does Rust's drop checker prevent dangling references in unsafe code?
The drop checker (dropck) ensures that values don't access invalid data in their Drop implementations. It requires that any data accessed during drop must outlive the value being dropped. This is enforced through PhantomData markers and can be relaxed using #[may_dangle] in unsafe code when you can prove the Drop implementation doesn't access certain generic parameters.
What is the difference between 'static and 'a lifetimes in trait bounds?
'static as a lifetime means data lives for the entire program duration, but as a trait bound (T: 'static), it means T owns all its data with no borrowed references. This is subtle: String is 'static even though it doesn't live forever, because it owns its data. Understanding this distinction is crucial for spawning threads and working with async code.