Busca en todas las páginas de la documentación
337 pages across 50 sections. 3134 questions total.
Because the borrow checker needs to know, at every point in the code, whether a binding could change. Immutable-by-default makes that knowledge the common case and marks the exception (mut) explicitly, which keeps the aliasing analysis tractable and makes intent visible in the signature.
No - the rules are not layered on top of a C++-like core, they are what the syntax was designed around from the start. Features like exhaustive match, no null, and move semantics as the default assignment behavior do not exist in C++ and require different mental models, not just stricter versions of the same ones.
match on an enum requires every variant to have an arm, checked at compile time. When a variant is added later, every existing match on that type becomes a compile error until updated, which turns "we forgot to handle the new case" from a runtime defect into a build failure caught immediately.
Because if, match, and blocks produce values, every branch that contributes to that value must type-check to the same type. This forces branches to agree on their result at compile time instead of relying on a variable being reassigned inconsistently across paths.
null is a value that claims to reference something but might not, with no way for the type system to track which. Rust replaces it with Option<T>, an ordinary enum, so "this might be absent" becomes part of the type and the compiler forces every use site to handle both cases.
No - checks that pass at compile time are erased from the binary, which is the point of a zero-cost abstraction. Runtime cost is not being traded for safety; the ownership model is largely what allows Rust to skip a garbage collector's overhead in the first place.
Because a compile error usually means "I could not prove this safe," and there is often a specific, mechanical fix (add a lifetime, clone a value, borrow instead of move). Suggesting it is a deliberate design goal, treating the compiler's output as a conversation rather than a dead end.
Not inherently - it is common even among experienced developers, especially when a design assumes shared mutable state that the ownership model does not allow as written. It usually signals a design that needs restructuring (borrowing instead of owning, splitting a struct, or using an explicit shared-ownership type), not personal failure.
It means a high-level construct compiles down to code no more expensive than the equivalent hand-written low-level code, with any safety checks resolved and erased at compile time. It does not mean the abstraction is free to learn or to write correctly.
Ownership is the thing all of this syntax exists to check. Immutability tells the checker what can change, expressions-as-values keep data flowing through typed channels the checker can follow, and exhaustive matching ensures every shape a value can take is accounted for - all facts the ownership and borrowing analysis in the next section depends on.
A narrow type carries fewer possible states, which means the compiler has less to prove wrong and the reader has less to infer. Option<T> instead of a nullable T, or a Shape enum instead of an untyped tag field, are both instances of shrinking the space of "what could this actually be" down to what the code truly allows.
Yes, deliberately, through explicit escape hatches - unsafe blocks, .unwrap(), Rc/RefCell for runtime-checked shared mutability. Each one is opt-in and visually distinct in the code, so relaxing a guarantee is a decision you can see, not a default you fall into.
Rust Basics puts these ideas into runnable code, and Ownership Preview starts unpacking the ownership model this page has been building toward. The full treatment lives in the Ownership & Lifetimes section.
It makes data flow easier to reason about and enables the borrow checker to prove safety. Mutation is explicit via mut.
Yes. let mut x = 1; let x = 2; creates a new immutable binding that hides the mutable one.
const values are inlined at compile time. They do not occupy stack space as variables at runtime.
static has a fixed memory address for the program lifetime. const is a compile-time value copied where used.
Yes. pub const TIMEOUT_MS: u64 = 5000; is common in library crates.
No. Shadowing creates a new binding. Reassignment (mut) updates the same binding.
Yes. let Point { x, y } = point; and let Some(v) = opt else { return }; (let-else) are idiomatic.
Even unused bindings can move ownership unless the type is Copy. Use _ prefix if you intentionally ignore a moved value.
Yes, but it only allows reassigning the local parameter binding. It does not affect the caller.
Avoid static mut. Prefer OnceLock, Mutex, or const data. static mut requires unsafe to read/write.
i32 when inference has no other constraint. Use explicit suffixes (42u64) in APIs that need a specific width.
Yes, with const generics: struct Buffer<const N: usize> { data: [u8; N] }.
let a = [0u8; 1024]; repeats 0u8 1024 times. The element must implement Copy.
No. bool is 1 byte for alignment and simple addressing.
Only for specific arities via standard impls (e.g. Clone for small tuples). Prefer structs for public APIs.
Pointer-sized signed integer. Use for indexing and len() results (usize).
"42".parse::<i32>() returns Result. See the strings section for FromStr.
No. Use Option<T> for absent values.
Yes, if T: PartialEq. [1,2] == [1,2] works element-wise.
GPU/math kernels where memory bandwidth matters. Default to f64 elsewhere.
Return a tuple, struct, or use out-parameters via &mut. Tuples are fine for 2-3 related values.
The empty tuple. main can return () or Result / ExitCode in binaries.
Yes. Use mut param only if you reassign the local binding inside the function.
No. Different names or generic traits provide polymorphism instead.
let v = if cond { a } else { b }; assigns the branch result to v.
fn(i32) -> i32 is a function pointer type. Closures with captures need Fn traits or Box<dyn Fn>.
Yes. Only the final expression (without ;) becomes the block's value.
Rust does not guarantee tail-call optimization. Prefer loops for deep recursion.
Use /// doc comments above the item. They support Markdown and appear in cargo doc.
Yes. fn main() -> Result<(), Box<dyn std::error::Error>> propagates errors with ?.
Yes. for x in &vec borrows elements. for x in &mut vec mutably borrows.
for (i, v) in items.iter().enumerate() is idiomatic. Raw 0..len works when neighbors are needed.
In loop, break 42 makes the loop expression evaluate to 42.
No. Only loop supports break value. Use loop or assign inside while.
Skips to the next iteration of the labeled loop, like labeled break.
match x { n if n > 0 => ..., _ => ... } adds a boolean condition to a pattern.
No. Use match for exhaustive pattern matching.
Yes for integers and chars: 1..=5, 'a'..='z'.
Not in Rust. Use break flags or Option return values instead.
for (k, v) in &map or map.iter(). Order is arbitrary unless you use BTreeMap.
Ownership and lifetimes free memory deterministically at compile time for most code, with zero runtime GC cost.
Scalars, tuples/arrays of Copy types, and types explicitly marked Copy. String and Vec are not Copy.
Clone explicitly. There is no implicit copy for non-Copy types.
A trait run automatically when a value goes out of scope. String frees heap memory in drop.
Passing by value moves. Passing by reference &T borrows without moving ownership.
Yes. Fields are owned like local variables. The struct is the owner of its fields.
A borrowed view into contiguous data: &[T] or &str. Slices do not own memory.
Moves are pointer copies for heap types. No reference counting unless you choose Rc/Arc.
Owned data moves into futures. Send/Sync bound shared state across tasks (see Concurrency section).
See the Ownership section starting with Ownership Basics.
&String coerces to &str, &Vec<T> to &[T] at function call sites when the target expects a slice.
Yes. &[] and &s[0..0] are valid empty slices.
len() is usize. Slices cannot exceed the underlying allocation.
Slices are references (fat pointers). Elements are not copied.
Yes. slice[0] = 1 mutates the underlying array or Vec.
Low-level pointer to first element. Used in unsafe/FFI, not typical application code.
slice.to_vec() for owned Vec. slice.to_owned() when T: Clone.
Not in safe Rust. Use Option<&str> for absence.
Slice/str fat pointer stores data pointer plus length (two words).
&str in a return type needs a lifetime tied to input. See the Ownership section.
No in safe application code. dyn Trait is still statically checked at compile time for trait object usage.
Sometimes. Annotate the return type on recursive functions explicitly.
i32 for untyped integer literals in most contexts.
The expression is still type-checked. _ discards the binding but not the type constraints.
Closure param/return types are inferred from usage. Annotate with |x: u32| -> u32 when needed.
Another name for explicit annotations: let x: i32 = 1.
Not directly. Use std::any::type_name::<T>() at runtime for debugging.
No. type Meters = f64 is interchangeable with f64. Use newtype struct for distinction.
Future output types are inferred from .await usage and return position in async fns.
rust-analyzer shows inferred types on hover. cargo check is the source of truth.
Yes. They serve different audiences. Many types derive Debug and manually impl Display.
Use print! instead of println!.
f.pad_integral, f.precision(), f.width() read specifiers inside fmt impls.
write! returns Result. println! panics on I/O error (rare for stdout).
{:?} works. For custom: match and format inner value.
Blanket impl exists: types with Display get to_string().
Not in std. Crates like owo-colors wrap Display output.
Prints file/line and expression value to stderr. Handy for quick debugging.
itertools::format or collect to Vec then join for separators.
Std has no i18n. Use external crates for locale rules.
No. Master functions, structs, enums, and match first. Macros come after reading standard patterns.
In prototypes, some cloning is fine. Before optimizing, profile and refactor APIs to borrow.
Not in fundamentals. Finish ownership, traits, and error handling first.
Short-term yes. Refactor into named helpers as you recognize repeated blocks.
Know i32, u32, i64, usize. Reach for explicit widths for protocols and FFI.
Small cargo new exercises, Advent of Code, or contributing doc tests to internal crates.
Helpful but not mandatory. These pages plus error-driven learning cover most day-one needs.
After comfortable with concrete types and traits. See Traits & Generics next.
Enable default lints early. Add pedantic selectively - do not fight every lint on day one.
Ownership deep dive, then structs/enums, traits, and error handling in parallel with practice.