Reading Rust
You do not need to write Rust fluently before you can read it. Reading comes first. This page is a decoder ring for the syntax you will see on every page of a Rust codebase.
A function signature, line by line
pub async fn parse_config<'a>(
input: &'a str,
strict: bool,
) -> Result<Config<'a>, ConfigError> {
/* ... */
}| Token | What it means |
|---|---|
pub | Public. Visible outside this module. |
async | Returns a Future. Must be .await-ed by the caller. |
fn | This is a function. |
parse_config | The name. |
<'a> | Generic over a lifetime parameter named 'a. |
input: &'a str | A borrowed string slice tied to 'a. |
strict: bool | A boolean by value. |
-> Result<Config<'a>, ConfigError> | Returns either Ok(Config<'a>) or Err(ConfigError). |
Once you can read this, you can read 90% of Rust signatures. The same shape repeats with different parts present or absent.
A struct
#[derive(Debug, Clone, PartialEq)]
pub struct Config<'a> {
pub host: &'a str,
pub port: u16,
pub options: Vec<String>,
}| Token | What it means |
|---|---|
#[derive(...)] | Auto-generated trait impls. Debug enables {:?} printing. Clone enables .clone(). PartialEq enables ==. |
pub struct Config<'a> | A public struct generic over 'a. |
pub host: &'a str | Field, public, borrowed string tied to 'a. |
pub port: u16 | Field, public, unsigned 16-bit int by value. |
pub options: Vec<String> | Field, public, owned vector of owned strings. |
The visibility (pub) on each field is per-field, not per-struct.
A method (impl block)
impl<'a> Config<'a> {
pub fn new(host: &'a str) -> Self {
Self {
host,
port: 8080,
options: Vec::new(),
}
}
pub fn with_port(mut self, port: u16) -> Self {
self.port = port;
self
}
}| Token | What it means |
|---|---|
impl<'a> Config<'a> | The methods that follow operate on Config<'a>. |
fn new(host: &'a str) -> Self | Constructor. Self is shorthand for the impl's type. |
Self { host, port: 8080, ... } | Struct literal. host is shorthand for host: host. |
fn with_port(mut self, port: u16) -> Self | Consumes self, returns a modified Self. The fluent builder pattern. |
Common symbols you will see everywhere
| Symbol | Meaning |
|---|---|
&T | Shared (read-only) reference to T. |
&mut T | Exclusive (read-write) reference to T. |
Box<T> | Owned heap allocation containing one T. |
Vec<T> | Owned growable array of T. |
Option<T> | Some(T) or None. Optional value. |
Result<T, E> | Ok(T) or Err(E). Success or failure. |
&str vs String | Borrowed string slice vs owned heap string. |
&[T] vs Vec<T> | Borrowed slice of T vs owned vector. |
? | Propagate error. Returns early if the Result is Err. |
! | Macro call (println!, vec!, format!). |
_ | Don't care / placeholder. |
'a | A lifetime parameter named a. |
dyn Trait | Dynamic dispatch through a trait object. |
impl Trait | "Some concrete type that implements Trait." Hides the actual type. |
where T: Trait | Constrains T to implement Trait. |
::<...> | Turbofish. Explicit type parameter at the call site. |
These ~20 symbols cover most Rust. Memorize them.
A real Sail snippet, decoded
From the Sail tour:
pub fn list_catalogs(&self, pattern: Option<&str>) -> CatalogResult<Vec<Arc<str>>> {
Ok(self
.state()?
.catalogs
.keys()
.filter(|name| match_pattern(name.as_ref(), pattern))
.cloned()
.collect::<Vec<_>>())
}Decoded line by line:
pub fn list_catalogs(&self, ...)— a public method borrowingself.pattern: Option<&str>— optional borrowed string slice. Can beSome("foo*")orNone.-> CatalogResult<Vec<Arc<str>>>— returns the crate's local result alias, wrapping an owned vector of refcounted strings.self.state()?— gets the mutex-guarded state.?propagates any error..catalogs.keys()— get an iterator over the keys..filter(...)— keep only the ones matching the pattern..cloned()— turn&Arc<str>intoArc<str>(cheap, refcount bump)..collect::<Vec<_>>()— gather into a Vec; the_lets Rust infer the inner type.
Five minutes ago this looked dense. Now it reads as a sentence: "for every catalog name that matches the pattern, clone the refcount, collect into a vec."
The five questions for any Rust file
When you open a file you have not seen:
- What does it own? Look for the public structs, enums, and traits.
- What does it borrow? Look at function signatures:
&T,&str,&[T]. - What does it return? Especially: which
Result<T, E>types? - What does it depend on? Read the
usestatements at the top. - Where is the entry point? A
mainfunction, a serverrun, aHandler::handle, animpl SomeTrait.
Five minutes of those questions tells you most of what the file is for.