Skip to content
← Back to explainers

Rust explainer

Learning Rust from another language

About 7 min to read. Allow extra time to work through examples.

Connect familiar programming ideas to Rust, and recognize where ownership, borrowing, types and error handling behave differently.

This article uses Rust. You can make it your practice mode to browse related content across the site.

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.

step 6 of 6

Orientation and the Gate

You've reached the last step

Revisit the track to review its lessons and exercises, or explore another topic.

Explore more tracks