Rust Interview Questions

35 questions with detailed answers

Question:
What is Rust and what are its main features?
Answer:
Rust is a systems programming language developed by Mozilla: • Memory safety without garbage collection • Zero-cost abstractions • Ownership system for memory management • Fearless concurrency • Cross-platform support • Fast performance comparable to C/C++ • Strong type system • Pattern matching • Trait-based generics • Excellent tooling with Cargo

Question:
Explain the basic data types in Rust.
Answer:
Rust has several primitive data types: • Integers: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize • Floating point: f32, f64 • Boolean: bool (true/false) • Character: char (Unicode scalar value) • Compound types: tuples and arrays • String types: String (owned) and &str (borrowed) • Unit type: () (empty tuple) Example: let x: i32 = 42; let y: f64 = 3.14; let is_rust: bool = true; let letter: char = 'R';

Question:
How do you declare variables and functions in Rust?
Answer:
Variable and function declaration in Rust: • Variables: let keyword (immutable by default) • Mutable variables: let mut keyword • Constants: const keyword • Functions: fn keyword Examples: // Variables let x = 5; // immutable let mut y = 10; // mutable const MAX: u32 = 100; // constant // Functions fn greet(name: &str) -> String { format!("Hello, {}!", name) } fn add(a: i32, b: i32) -> i32 { a + b // implicit return }

Question:
Explain ownership in Rust and how it prevents memory leaks.
Answer:
Ownership in Rust: • Each value has a single owner • When owner goes out of scope, value is dropped • Prevents double-free and use-after-free errors • No garbage collector needed • Move semantics by default Example: fn main() { let s1 = String::from("hello"); let s2 = s1; // s1 moved to s2 // println!("{}", s1); // Error: s1 no longer valid println!("{}", s2); // OK }

Question:
What is borrowing in Rust and what are the borrowing rules?
Answer:
Borrowing allows references without taking ownership: • References don't take ownership • Multiple immutable references allowed • Only one mutable reference at a time • No mixing mutable and immutable references • References must be valid (no dangling) Example: fn main() { let mut s = String::from("hello"); let r1 = &s; // immutable borrow let r2 = &s; // OK: multiple immutable let r3 = &mut s; // Error: can't borrow as mutable }

Question:
Explain the difference between String and &str in Rust.
Answer:
String vs &str differences: • String: owned, heap-allocated, growable • &str: borrowed, string slice, immutable • String can be modified, &str cannot • &str is more efficient for read-only operations • String literals are &str type Example: let owned: String = String::from("hello"); let borrowed: &str = "world"; let slice: &str = &owned[0..2]; fn takes_str(s: &str) { } fn takes_string(s: String) { }

Question:
How does pattern matching work in Rust with match expressions?
Answer:
Pattern matching in Rust: • Exhaustive matching required • Destructures data types • Guards with if conditions • Wildcard _ for catch-all • Must handle all cases Example: enum Coin { Penny, Nickel, Dime, Quarter } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } }

Question:
Explain Option and Result types in Rust.
Answer:
Option and Result for safe error handling: • Option: Some(T) or None • Result: Ok(T) or Err(E) • No null pointer exceptions • Explicit error handling • Chainable with combinators Example: fn divide(a: f64, b: f64) -> Result { if b == 0.0 { Err("Division by zero".to_string()) } else { Ok(a / b) } } match divide(10.0, 2.0) { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e), }

Question:
What are traits in Rust and how do they enable polymorphism?
Answer:
Traits define shared behavior: • Similar to interfaces in other languages • Define method signatures • Can have default implementations • Enable polymorphism without inheritance • Trait objects for dynamic dispatch Example: trait Summary { fn summarize(&self) -> String; } struct Article { title: String, content: String, } impl Summary for Article { fn summarize(&self) -> String { format!("{}: {}", self.title, self.content) } }

Question:
Explain lifetimes in Rust and when they are needed.
Answer:
Lifetimes ensure reference validity: • Prevent dangling references • Explicit lifetime annotations • Lifetime elision rules • 'static lifetime for program duration • Generic lifetime parameters Example: fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } // Usage let string1 = String::from("long string"); let string2 = "short"; let result = longest(&string1, string2);

Question:
How do closures work in Rust and what are the capture modes?
Answer:
Closures are anonymous functions: • Can capture environment variables • Three capture modes: by reference, by mutable reference, by value • Implement Fn, FnMut, or FnOnce traits • Type inference for parameters • Move keyword for ownership transfer Example: let x = 4; let equal_to_x = |z| z == x; // captures x by reference let mut list = vec![1, 2, 3]; let mut borrows_mutably = || list.push(7); // captures by mutable reference let moves = move || println!("x: {}", x); // moves x into closure

Question:
Explain iterators in Rust and their lazy evaluation.
Answer:
Iterators in Rust: • Lazy evaluation - no work until consumed • Iterator trait with next() method • Adapters transform iterators • Consumers execute the iterator • Zero-cost abstractions Example: let v = vec![1, 2, 3, 4, 5]; // Lazy - no work done yet let iter = v.iter() .map(|x| x * 2) .filter(|&x| x > 4); // Consumer - work is done here let results: Vec = iter.collect(); // Chain operations let sum: i32 = v.iter().map(|x| x * x).sum();

Question:
What is the difference between Vec and arrays in Rust?
Answer:
Vec vs Arrays: • Arrays: fixed size, stack allocated, [T; N] • Vec: dynamic size, heap allocated, Vec • Arrays known at compile time • Vec can grow and shrink • Different indexing behavior Example: // Array let arr: [i32; 5] = [1, 2, 3, 4, 5]; let slice = &arr[1..3]; // Vec let mut vec = Vec::new(); vec.push(1); vec.push(2); let element = vec.pop(); // returns Option // Convert let vec_from_array = arr.to_vec();

Question:
How does error propagation work with the ? operator?
Answer:
Error propagation with ?: • Shorthand for match on Result • Returns early on Err • Continues on Ok • Only works in functions returning Result or Option • Automatic type conversion with From trait Example: use std::fs::File; use std::io::Read; fn read_file() -> Result { let mut file = File::open("file.txt")?; // returns early if Err let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } // Equivalent to: // match File::open("file.txt") { // Ok(file) => file, // Err(e) => return Err(e), // }

Question:
Explain struct methods and associated functions in Rust.
Answer:
Methods and associated functions: • Methods take &self, &mut self, or self • Associated functions don't take self • Defined in impl blocks • :: syntax for associated functions • . syntax for methods Example: struct Rectangle { width: u32, height: u32, } impl Rectangle { // Associated function (constructor) fn new(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } // Method fn area(&self) -> u32 { self.width * self.height } } let rect = Rectangle::new(10, 20); let area = rect.area();

Question:
What are enums in Rust and how do they differ from other languages?
Answer:
Enums in Rust: • Algebraic data types • Variants can hold data • Each variant can have different types • Pattern matching required • Memory efficient Example: enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn process_message(msg: Message) { match msg { Message::Quit => println!("Quit"), Message::Move { x, y } => println!("Move to {}, {}", x, y), Message::Write(text) => println!("Text: {}", text), Message::ChangeColor(r, g, b) => println!("RGB: {}, {}, {}", r, g, b), } }

Question:
How do you handle collections and their common operations in Rust?
Answer:
Common collections in Rust: • Vec: growable arrays • HashMap: key-value pairs • HashSet: unique values • BTreeMap/BTreeSet: ordered collections • VecDeque: double-ended queue Example: use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Red"), 50); // Access let score = scores.get("Blue"); // Iterate for (key, value) in &scores { println!("{}: {}", key, value); } // Update scores.entry(String::from("Yellow")).or_insert(50);

Question:
Explain Rust memory model and how it achieves memory safety without garbage collection.
Answer:
Rust's memory model and safety: • Ownership system prevents data races at compile time • Each value has exactly one owner • Borrowing allows references without taking ownership • Lifetimes ensure references are always valid • No garbage collector needed • RAII (Resource Acquisition Is Initialization) • Zero-cost abstractions Key safety guarantees: • No use-after-free • No double-free • No null pointer dereference • No buffer overflows • No data races Example: fn main() { let s1 = String::from("hello"); let s2 = s1; // s1 moved to s2 // println!("{}", s1); // Compile error! let s3 = String::from("world"); let len = calculate_length(&s3); // Borrow println!("{} has length {}", s3, len); // s3 still valid } fn calculate_length(s: &String) -> usize { s.len() } // s goes out of scope but doesn't drop the String

Question:
What are smart pointers in Rust and when would you use each type?
Answer:
Smart pointers in Rust: • Box: Heap allocation for single ownership • Rc: Reference counting for shared ownership (single-threaded) • Arc: Atomic reference counting (thread-safe) • RefCell: Interior mutability with runtime borrow checking • Mutex: Thread-safe interior mutability • Weak: Non-owning references to break cycles When to use each: • Box: Large data, recursive types, trait objects • Rc: Multiple owners in single-threaded code • Arc: Multiple owners across threads • RefCell: Mutate through immutable references • Mutex: Shared mutable state across threads Example: use std::rc::Rc; use std::cell::RefCell; // Shared ownership with interior mutability let data = Rc::new(RefCell::new(vec![1, 2, 3])); let data1 = Rc::clone(&data); let data2 = Rc::clone(&data); data.borrow_mut().push(4); println!("{:?}", data1.borrow()); // [1, 2, 3, 4] // Recursive type with Box enum List { Cons(i32, Box), Nil, } let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

Question:
Explain unsafe Rust and when it is necessary to use unsafe blocks.
Answer:
Unsafe Rust capabilities and usage: • Dereference raw pointers • Call unsafe functions or methods • Access or modify mutable static variables • Implement unsafe traits • Access fields of unions When unsafe is necessary: • FFI (Foreign Function Interface) • Low-level system programming • Performance-critical optimizations • Implementing safe abstractions • Hardware interaction • Custom allocators Safety rules still apply: • Memory safety is your responsibility • Data race prevention • Proper initialization • Lifetime management Example: // Safe wrapper around unsafe operations fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = slice.len(); let ptr = slice.as_mut_ptr(); assert!(mid <= len); unsafe { ( std::slice::from_raw_parts_mut(ptr, mid), std::slice::from_raw_parts_mut(ptr.add(mid), len - mid), ) } } // FFI example extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("Absolute value of -3: {}", abs(-3)); } }

Question:
How does Rust handle concurrency and what are the main concurrency primitives?
Answer:
Rust concurrency model: • Fearless concurrency through ownership • Send trait: types safe to transfer between threads • Sync trait: types safe to share between threads • No data races at compile time • Message passing and shared state patterns Concurrency primitives: • std::thread: OS threads • mpsc channels: message passing • Mutex: mutual exclusion • RwLock: reader-writer lock • Arc: atomic reference counting • Atomic types: lock-free operations Message passing example: use std::sync::mpsc; use std::thread; let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!("Got: {}", received); Shared state example: use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap());

Question:
Explain Rust type system and how it prevents common programming errors.
Answer:
Rust's type system and error prevention: • Static typing with inference • Ownership and borrowing system • Algebraic data types (enums) • Trait system for polymorphism • No null pointers (Option) • Explicit error handling (Result) • Pattern matching exhaustiveness Common errors prevented: • Null pointer dereference → Option • Use after free → Ownership • Double free → Ownership • Data races → Send/Sync traits • Buffer overflows → Bounds checking • Memory leaks → RAII and Drop Example - Null safety: // No null pointers in Rust fn get_user(id: u32) -> Option { if id == 0 { None } else { Some(User { id, name: "Alice".to_string() }) } } match get_user(1) { Some(user) => println!("User: {}", user.name), None => println!("User not found"), } Example - Error handling: fn divide(a: f64, b: f64) -> Result { if b == 0.0 { Err("Division by zero".to_string()) } else { Ok(a / b) } } match divide(10.0, 2.0) { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e), }

Question:
How do you implement custom iterators in Rust?
Answer:
Custom iterators in Rust: • Implement the Iterator trait • Define associated Item type • Implement next() method • Lazy evaluation by default • Chainable with adapter methods • Zero-cost abstractions Iterator trait: trait Iterator { type Item; fn next(&mut self) -> Option; } Example - Counter iterator: struct Counter { current: usize, max: usize, } impl Counter { fn new(max: usize) -> Counter { Counter { current: 0, max } } } impl Iterator for Counter { type Item = usize; fn next(&mut self) -> Option { if self.current < self.max { let current = self.current; self.current += 1; Some(current) } else { None } } } // Usage let counter = Counter::new(5); for num in counter { println!("{}", num); // 0, 1, 2, 3, 4 } // With iterator adapters let sum: usize = Counter::new(5) .map(|x| x * x) .filter(|&x| x % 2 == 0) .sum(); println!("Sum of even squares: {}", sum);

Question:
What is the difference between Copy and Clone traits?
Answer:
Copy vs Clone traits: • Copy: Implicit bitwise copy, no heap allocation • Clone: Explicit deep copy, can involve heap allocation • Copy implies Clone automatically • Copy types: integers, floats, booleans, chars, tuples/arrays of Copy types • Non-Copy types: String, Vec, HashMap, etc. Copy trait: • Marker trait (no methods) • Automatic bitwise copy • Move semantics disabled • Stack-only data • Cannot implement Drop Clone trait: • Explicit clone() method • Can be expensive • Deep copy semantics • Works with heap data Example: #[derive(Copy, Clone)] struct Point { x: i32, y: i32, } #[derive(Clone)] struct Person { name: String, age: u32, } fn main() { // Copy types let p1 = Point { x: 1, y: 2 }; let p2 = p1; // Copied, p1 still valid println!("p1: ({}, {})", p1.x, p1.y); // Clone types let person1 = Person { name: "Alice".to_string(), age: 30, }; let person2 = person1.clone(); // Explicit clone // person1 still valid after clone println!("person1: {}", person1.name); // String example let s1 = String::from("hello"); let s2 = s1.clone(); // Explicit clone needed println!("s1: {}, s2: {}", s1, s2); }

Question:
Explain Rust workspace and package management.
Answer:
Rust workspace and package management: • Workspace: Multiple packages in single repository • Package: Crate with Cargo.toml • Crate: Compilation unit (binary or library) • Cargo: Build tool and package manager • Shared dependencies and lock file Workspace structure: workspace/ ├── Cargo.toml # Workspace manifest ├── Cargo.lock # Shared lock file ├── package1/ │ ├── Cargo.toml │ └── src/ └── package2/ ├── Cargo.toml └── src/ Workspace Cargo.toml: [workspace] members = [ "package1", "package2", ] [workspace.dependencies] serde = "1.0" tokio = "1.0" Package Cargo.toml: [package] name = "package1" version = "0.1.0" edition = "2021" [dependencies] serde = { workspace = true } package2 = { path = "../package2" } Benefits: • Shared dependencies • Unified versioning • Cross-package development • Single Cargo.lock • Coordinated releases Commands: cargo build # Build all packages cargo test # Test all packages cargo build -p package1 # Build specific package cargo publish -p package1 # Publish specific package

Question:
How do you handle cross-compilation in Rust?
Answer:
Cross-compilation in Rust: • Target triple specification (arch-vendor-os) • Toolchain installation • Linker configuration • Platform-specific dependencies • Conditional compilation Common targets: • x86_64-pc-windows-gnu (Windows) • x86_64-apple-darwin (macOS) • x86_64-unknown-linux-gnu (Linux) • aarch64-apple-darwin (Apple Silicon) • wasm32-unknown-unknown (WebAssembly) Setup process: # Install target rustup target add x86_64-pc-windows-gnu # Install cross-compilation tools # For Windows on Linux: sudo apt-get install gcc-mingw-w64 # Configure linker in .cargo/config.toml [target.x86_64-pc-windows-gnu] linker = "x86_64-w64-mingw32-gcc" Build commands: cargo build --target x86_64-pc-windows-gnu cargo build --target wasm32-unknown-unknown Conditional compilation: #[cfg(target_os = "windows")] fn platform_specific() { println!("Running on Windows"); } #[cfg(target_os = "linux")] fn platform_specific() { println!("Running on Linux"); } #[cfg(target_arch = "wasm32")] fn wasm_specific() { // WebAssembly specific code } Cargo.toml dependencies: [target.'cfg(windows)'.dependencies] winapi = "0.3" [target.'cfg(unix)'.dependencies] libc = "0.2"

Question:
What are the best practices for Rust project structure?
Answer:
Rust project structure best practices: • Standard directory layout • Clear module organization • Separation of concerns • Documentation and examples • Testing strategy Standard structure: project/ ├── Cargo.toml # Package manifest ├── Cargo.lock # Dependency lock file ├── README.md # Project documentation ├── LICENSE # License file ├── src/ │ ├── lib.rs # Library root (for libraries) │ ├── main.rs # Binary root (for executables) │ ├── bin/ # Additional binaries │ └── modules/ # Module files ├── tests/ # Integration tests ├── examples/ # Example code ├── benches/ # Benchmarks └── docs/ # Additional documentation Module organization: // lib.rs pub mod config; pub mod database; pub mod api; pub use config::Config; pub use database::Database; // main.rs use myproject::{Config, Database}; fn main() { let config = Config::load(); let db = Database::connect(&config); } Cargo.toml structure: [package] name = "myproject" version = "0.1.0" edition = "2021" authors = ["Your Name "] license = "MIT" description = "A brief description" repository = "https://github.com/user/repo" keywords = ["rust", "example"] categories = ["development-tools"] [dependencies] serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.0", features = ["full"] } [dev-dependencies] tokio-test = "0.4" Best practices: • Use descriptive module names • Keep functions small and focused • Document public APIs • Write comprehensive tests • Use semantic versioning • Include examples and benchmarks

Question:
What are macros in Rust and how do they differ from functions?
Answer:
Macros in Rust: Metaprogramming - code that writes code, compile-time code generation, pattern matching on syntax, hygiene prevents variable capture. Types: Declarative macros (macro_rules!) and Procedural macros. Differences from functions: Operate on syntax not values, variable arguments, expanded at compile time, can generate any valid Rust code, no runtime overhead. Example: macro_rules! vec creates vectors at compile time.

Question:
Explain async/await in Rust and how it differs from other languages.
Answer:
Async/await in Rust: Zero-cost futures, cooperative multitasking, no built-in runtime, compile-time transformation, Pin for self-referential types. Key concepts: Future trait, executors drive futures, async fn returns impl Future, .await suspends execution. Differences from other languages: No built-in runtime, zero allocation for simple futures, compile-time async transformation, explicit executor choice.

Question:
How does Rust module system work and what are the visibility rules?
Answer:
Rust module system: Hierarchical module tree, privacy by default, explicit visibility control, path-based imports. Visibility levels: private (default), pub (public to parent), pub(crate) (public within crate), pub(super) (public to parent), pub(in path) (public to specific path). Use declarations for imports, re-exports with pub use.

Question:
What is the difference between static and const in Rust?
Answer:
Static vs const in Rust: const is compile-time constant, inlined at usage sites, no memory address, must be computable at compile time, always immutable. static is runtime constant with memory address, single instance in memory, can be mutable with unsafe, has static lifetime. Use const for mathematical constants, static for global state and singletons.

Question:
Explain trait objects and dynamic dispatch in Rust.
Answer:
Trait objects and dynamic dispatch: Runtime polymorphism in Rust using dyn Trait syntax, virtual function table (vtable), fat pointers (data + vtable). Object safety requirements: No Self parameters except receiver, no generic type parameters, no associated functions. Example: Vec> allows different types implementing Draw trait to be stored together with runtime dispatch.

Question:
How does Rust handle zero-cost abstractions?
Answer:
Zero-cost abstractions in Rust: High-level features with no runtime overhead, compile-time optimizations, monomorphization of generics, inlining and dead code elimination. Examples: iterators compile to manual loops, generic functions specialized per type, Option optimization uses null pointer optimization. Principle: What you do not use, you do not pay for.

Question:
Explain Rust approach to error handling and custom error types.
Answer:
Rust error handling: No exceptions - explicit error handling with Result for recoverable errors, panic! for unrecoverable errors, ? operator for error propagation. Custom error types implement Error trait and Display trait. From trait enables automatic error conversion. Error chaining with source() method for debugging.

Question:
What are phantom types and when would you use them?
Answer:
Phantom types in Rust: Types that carry compile-time information with zero runtime cost, used for type-level programming, state machines, units of measurement. PhantomData is zero-sized marker type. Examples: Distance vs Distance for type-safe unit conversion, Door vs Door for state machine safety at compile time.
Study Tips
  • Read each question carefully
  • Try to answer before viewing the solution
  • Practice explaining concepts out loud
  • Review regularly to reinforce learning
Share & Practice

Found this helpful? Share with others!

Feedback