Your previous language gives you useful starting points for Rust, but some familiar-looking syntax behaves differently. Use the sections below to spot those differences. They are comparisons to explore, not predictions about how long you will take to learn.
Concepts to keep separate
Ownership determines who is responsible for a value. Moving a non-Copy value transfers ownership; the old binding cannot be used as though it still owns that value. Borrowing gives access without transferring ownership. Shared and exclusive access have different rules.
Lifetimes describe how long references are valid and how input and output references relate. An annotation does not extend the life of the storage. Many signatures need no explicit annotations because lifetime elision supplies the relationship.
Async allows a task to suspend while waiting for progress. It is one way to organize concurrency, not a synonym for parallel execution. Futures, polling and waking describe how work progresses; an executor coordinates those operations. Learning threads does not require first learning async, and neither model removes ownership requirements.
The course revisits these ideas through exercises. Follow each exercise's actual signatures and constraints. Some require borrowing or restrict allocations; others permit owned copies. There is no universal point after which cloning becomes forbidden.
Coming from C or C++
Familiarity with memory layout and pointer validity helps. If you use C++,
resource-owning types and destructors also provide useful connections to
Rust's ownership and Drop. C and C++ differ here, so do not assume that
every feature in this comparison belongs to both languages.
In Rust, moving a non-Copy value prevents subsequent use through its old binding until it is validly reinitialized. Do not carry over assumptions about using a moved-from C++ object. Partial moves and Copy values add further distinctions as you encounter them.
Borrow checking can reject code whose intended behavior you believe is safe. Inspect the conflicting accesses and their scopes. Often the repair is to end a borrow sooner, split the data differently or change what a function returns. An unsafe block is not a general way to turn off the borrow checker.
Trait implementations also have coherence rules. A crate cannot freely add
an implementation of an external trait for an external type, such as
Display for Vec<T>. Local wrapper types are often useful. The full orphan
rules include details about generic parameters and special wrapper types,
so the shorthand "own the trait or type" is only a starting point.
Cargo and Rust modules replace many familiar project conventions, but build configuration, native dependencies and linking can still need attention.
Coming from Java, C#, Go or another managed language
Static types, interfaces and generic APIs may feel familiar. Rust traits serve some similar purposes, but their implementation and dispatch rules are different from any one language's interfaces.
Automatic memory reclamation does not eliminate questions about resource
ownership in other languages. Rust makes more of those relationships part
of the type system. Start by distinguishing an owned String from a
borrowed &str: does the function need to retain the data, transform it or
only inspect it during the call?
Cloning can be appropriate when two independent owned values are needed. It can also hide unnecessary copying. First make the ownership requirement clear, then assess the cost; a clone is neither automatically wrong nor a universal fix for a borrow error.
Use Option<T> to represent a value that may be absent. Safe references
cannot be null, although Rust's raw pointers can. Use Result<T, E> for an
operation that succeeds or returns an error. The ? operator propagates a
compatible residual, commonly returning an error from the enclosing
function. It does not itself throw an exception or panic.
Error values can carry structured information. This is not unique to Rust: Go errors, for example, are not restricted to plain strings.
Coming from Python, Ruby or JavaScript
Pay attention to the type expected at each expression. Rust conditions
require bool; integers are not treated as true or false. Integer types do
not generally widen implicitly to match an expected type. Rust does have
coercions and type inference, so "there are no implicit conversions" would
be too broad.
A match must cover the possibilities of the value being matched. Option
and Result make this useful: success, failure and absence are visible
cases rather than values to remember to check by convention.
Iterator pipelines will be familiar if you use comprehensions or lazy
sequence tools. Operations such as map and filter build adapters;
consuming the iterator performs the work. Watch whether an iterator yields
owned values, shared references or mutable references.
Check arithmetic semantics when translating code. In Python, -7 % 2 is
1; in Rust it is -1. Rust's rem_euclid gives a nonnegative remainder
for this integer example. Try
clock positions
to practice wrapping negative values into a range.
Coming from Haskell, OCaml or F#
Enums with payloads, pattern matching and explicit optional or error values
offer useful points of comparison. Rust traits overlap with some uses of
type classes, but their rules are not interchangeable. ? performs early
propagation in supported types; it is not a general monadic bind operator.
Ownership matters when composing closures. How a closure captures a value
and how it uses that value are separate questions. A move closure can
still implement Fn if it only reads its captures. Mutating captures may
require FnMut; moving a non-Copy capture out can limit it to FnOnce.
Rust does not directly provide general higher-kinded type parameters. Some
abstractions can be expressed with associated types, generic associated
types or a different API. A dyn Trait object also requires a dyn-compatible
trait; not every generic abstraction can become a trait object.
A practical way to work through an exercise
Read the signature, examples and failure conditions before editing. Predict what the starter does, then compare that prediction with the compiler or test output. Diagnostics often explain both the conflicting types and the relevant source locations. Suggested fixes still need to match the task.
Change one cause at a time. If an ownership error is unclear, draw the owner and the references, then mark the last use of each. Consider returning owned data, shortening a borrow or storing an index when those choices fit the operation.
The Rust grading checks treat warnings as errors, so distinguish a compiler error from a lint that is promoted by project policy. Neither means the whole design needs replacing. Start with reading a compiler error and use small examples to test one rule at a time.