Functions#
A function in Rust is declared with fn, has typed
parameters, an optional return type (-> T), and a body
that is itself an expression. The implicit return value is the
final expression (no trailing ;). Functions can be generic
over types and lifetimes, async, const, and unsafe.
/types`.
For methods (functions on a type), see OOP. For async
runtime mechanics, see Concurrency.
Declaration#
fn add(a: i32, b: i32) -> i32 {
a + b // implicit return; no `;`
}
fn shout(s: &str) -> String {
s.to_uppercase()
}
fn print_args(args: &[String]) { // -> () implied
for a in args { println!("{a}"); }
}
Annotate every parameter and the return type; inference does not reach across function boundaries.
Parameters#
Pass by value (move or copy), shared reference (&T), or
exclusive reference (&mut T). Pick based on
ownership intent.
fn consume(s: String) { /* takes ownership */ }
fn read(s: &String) { /* borrows; better as &str */ }
fn read_str(s: &str) { /* prefer this */ }
fn modify(v: &mut Vec<i32>){ v.push(0); }
Default values are not built in; use Option<T> or an
Options struct with Default.
fn fetch(url: &str, timeout: Option<Duration>) { /* … */ }
fetch("https://x", None);
Returns#
The final expression of the body is the return value. return
expr; also works, mostly for early returns.
fn classify(n: i32) -> &'static str {
if n < 0 { return "negative"; }
if n == 0 { return "zero"; }
"positive"
}
Multiple returns: tuples or structs.
fn divmod(a: i32, b: i32) -> (i32, i32) { (a / b, a % b) }
let (q, r) = divmod(17, 5);
Closures#
|params| body. Closures capture surrounding bindings; the
compiler picks how (by reference, by mutable reference, by
move) based on what the body needs.
let n = 10;
let add_n = |x: i32| x + n;
println!("{}", add_n(5)); // 15
let mut total = 0;
let mut add = |x| total += x;
add(1); add(2); add(3);
println!("{total}"); // 6
let owned = String::from("hi");
let print = move || println!("{owned}"); // forces capture by move
Closures implement one of three traits.
Trait |
When |
|---|---|
|
reads captured bindings; can be called many times. |
|
mutates captured bindings; can be called many times. |
|
moves captured bindings; can be called once. |
Take closures by trait bound or trait object.
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
fn apply_mut<F: FnMut(&str)>(mut f: F, items: &[&str]) {
for x in items { f(x); }
}
let boxed: Box<dyn Fn(i32) -> i32> = Box::new(|x| x + 1);
Generics#
Type parameters in <>; constraints with T: Trait or in
a where clause.
fn head<T>(xs: &[T]) -> Option<&T> {
xs.first()
}
fn max<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
fn sort_strings<T>(xs: &mut [T])
where T: AsRef<str> + Ord {
xs.sort();
}
impl Trait#
Argument position: f(x: impl Trait) is sugar for
f<T: Trait>(x: T). Return position: -> impl Trait lets
the operator return an unnamed type that implements the trait
(common for iterators and futures).
fn double_all(xs: &[i32]) -> impl Iterator<Item = i32> + '_ {
xs.iter().map(|x| x * 2)
}
The caller cannot name the return type, but can call any method on it that the trait provides.
Lifetimes in signatures#
When the compiler cannot tell which input lifetime an output reference comes from, the operator annotates explicitly.
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
struct Wrap<'a> { name: &'a str }
impl<'a> Wrap<'a> {
fn name(&self) -> &'a str { self.name }
}
async functions#
async fn returns a Future; calling the function does
not run it. The future is driven by an executor (tokio,
async-std, smol). See Concurrency.
async fn fetch(url: &str) -> reqwest::Result<String> {
let body = reqwest::get(url).await?.text().await?;
Ok(body)
}
#[tokio::main]
async fn main() -> reqwest::Result<()> {
let body = fetch("https://example.com").await?;
println!("{body}");
Ok(())
}
const fn#
const fn can be called at compile time as well as runtime.
Used for table lookups, sized constants, and static /
const initialisers.
const fn buffer_size(level: u32) -> usize {
1 << (level + 10)
}
const BUF: usize = buffer_size(3);
unsafe fn#
unsafe fn marks a function whose caller must uphold
invariants the compiler cannot check. Pointer dereferences,
FFI, manual aliasing, calls to other unsafe functions live
inside unsafe blocks or unsafe fn.
unsafe fn deref(p: *const i32) -> i32 { *p }
let n = 10_i32;
let p: *const i32 = &n;
unsafe { println!("{}", deref(p)); }
Wrap minimal unsafe blocks in safe APIs;
nothing outside the wrapper needs the unsafe keyword.
References#
Syntax for
fnand->lexical rules.Types for parameter / return types, generics,
impl Trait.OOP for methods,
self, and trait implementations.Concurrency for
async fnandFuture.Errors for
Result,Option,?.