Rustlings Arena
← 返回竞技场

Rust 速查表

变量、所有权、借用、结构体、枚举、特征、泛型、生命周期、迭代器、闭包的快速语法参考。

Variables & Mutability

let x = 5;             // immutable (default)
let mut y = 5;         // mutable
let z: i32 = 5;        // explicit type

// Shadowing — rebind with new value/type
let x = x + 1;        // shadow x (still immutable)
let x = x.to_string(); // shadow to change type

// Constants — compile-time, must have type
const MAX: u32 = 100_000;

// Destructuring
let (a, b, c) = (1, 2, 3);
let Point { x, y } = point;
▶ Try it

Primitive Types

// Integers
i8  i16  i32  i64  i128  isize   // signed
u8  u16  u32  u64  u128  usize   // unsigned
let n: i32 = 1_000_000;          // underscores for readability
let h = 0xff;   let b = 0b1010;  // hex, binary

// Floats
f32  f64 (default)
let pi: f64 = 3.14159;

// Boolean
let t: bool = true;

// Character (Unicode scalar, 4 bytes)
let c: char = '🦀';

// Tuples
let tup: (i32, f64, bool) = (1, 2.0, true);
let first = tup.0;

// Arrays — fixed length, same type
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10];   // 10 zeros

Control Flow

// if / else if / else — also an expression
let x = if condition { 1 } else { 2 };

// loop — infinite, can return a value
let result = loop {
    if done { break 42; }
};

// while
while n < 100 { n *= 2; }

// while let
while let Some(x) = stack.pop() { println!("{}", x); }

// for — iterating
for item in collection { ... }
for i in 0..10 { ... }        // 0 to 9
for i in 0..=10 { ... }       // 0 to 10
for (i, v) in v.iter().enumerate() { ... }

// match — exhaustive
match value {
    1        => println!("one"),
    2 | 3    => println!("two or three"),
    4..=9    => println!("four to nine"),
    x if x < 0 => println!("negative: {}", x),
    _        => println!("other"),
}

// if let — match one pattern
if let Some(n) = opt { println!("{}", n); }
if let Ok(v) = result { println!("{}", v); }

Functions

fn add(a: i32, b: i32) -> i32 {
    a + b         // last expression returned (no semicolon)
}

// Early return
fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { return None; }
    Some(a / b)
}

// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T { ... }

// Multiple return values via tuple
fn min_max(v: &[i32]) -> (i32, i32) { (v[0], v[v.len()-1]) }

// Diverging function (never returns)
fn crash(msg: &str) -> ! { panic!("{}", msg); }
▶ Try it

Ownership & Move

// Heap types MOVE on assignment
let s1 = String::from("hello");
let s2 = s1;           // s1 MOVED — cannot use s1 anymore

// Clone for a deep copy
let s2 = s1.clone();   // both s1 and s2 valid

// Copy types are implicitly duplicated (stack types)
// i32, u32, f64, bool, char, tuples of Copy types
let x = 5;
let y = x;             // x still valid — Copy

// Ownership through functions
fn takes(s: String) { ... }  // s moved in, dropped on return
fn gives() -> String { String::from("hi") }  // moves out
▶ Try it

Borrowing & References

// Immutable borrow — many allowed at once
let len = calculate_length(&s1);
fn calculate_length(s: &String) -> usize { s.len() }

// Mutable borrow — exactly one, no others active
fn change(s: &mut String) { s.push_str(" world"); }
change(&mut s);

// Rules:
// • Many & refs OR one &mut ref — never both
// • References must always be valid (no dangling refs)

// Prefer &str over &String in parameters:
fn greet(name: &str) { ... }  // accepts &String AND &"literal"
greet("Alice");
greet(&owned_string);

Slices

let s = String::from("hello world");
let hello = &s[0..5];   // "hello"
let world = &s[6..];    // "world"
let all   = &s[..];     // whole string

// &str is a string slice
let literal: &str = "hello";  // points to read-only memory

// Array / Vec slices
let a = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];  // [2, 3]
▶ Try it

Structs

struct Point { x: f64, y: f64 }

// Instantiate
let p = Point { x: 1.0, y: 2.0 };
println!("{}", p.x);

// Update syntax
let p2 = Point { x: 3.0, ..p };

// Tuple struct
struct Color(u8, u8, u8);
let red = Color(255, 0, 0);

// Unit struct (no fields)
struct Marker;

// Methods
impl Point {
    // Associated function (no self) — like a static method
    fn origin() -> Self { Point { x: 0.0, y: 0.0 } }

    fn distance(&self, other: &Point) -> f64 {   // immutable
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }

    fn translate(&mut self, dx: f64, dy: f64) {  // mutable
        self.x += dx;
        self.y += dy;
    }
}

Enums & Pattern Matching

enum Message {
    Quit,                        // no data
    Move { x: i32, y: i32 },    // named fields
    Write(String),               // one value
    Color(u8, u8, u8),          // tuple
}

match msg {
    Message::Quit               => println!("quit"),
    Message::Move { x, y }     => println!("{},{}", x, y),
    Message::Write(s)           => println!("{}", s),
    Message::Color(r, g, b)    => println!("rgb({},{},{})", r, g, b),
}

// Destructuring in let
let Message::Write(text) = msg else { return; };  // Rust 1.65+

// @ binding — capture and test
match n {
    x @ 1..=10 => println!("small: {}", x),
    _           => println!("big"),
}

Option & Result

// Option<T> — Some(value) or None
let opt: Option<i32> = Some(42);
let none: Option<i32> = None;

opt.unwrap()               // panics if None
opt.unwrap_or(0)           // default value
opt.unwrap_or_else(|| 0)   // lazy default
opt.map(|x| x * 2)         // transform Some, pass None through
opt.and_then(|x| if x > 0 { Some(x) } else { None })
opt.filter(|&x| x > 0)
opt.is_some()  opt.is_none()

// Result<T, E> — Ok(value) or Err(error)
let ok: Result<i32, &str> = Ok(42);
let err: Result<i32, &str> = Err("oops");

ok.unwrap()
ok.unwrap_or(0)
ok.map(|x| x * 2)
ok.map_err(|e| format!("Error: {}", e))
ok.is_ok()  ok.is_err()

// Convert between them
opt.ok_or("missing")       // Option -> Result
result.ok()                // Result -> Option

Error Handling

use std::num::ParseIntError;

// ? propagates errors up the call stack
fn parse(s: &str) -> Result<i32, ParseIntError> {
    let n = s.trim().parse::<i32>()?;  // returns Err if parse fails
    Ok(n * 2)
}

// Box<dyn Error> for multiple error types
use std::error::Error;
fn run() -> Result<(), Box<dyn Error>> {
    let n: i32 = "42".parse()?;
    let f = std::fs::read_to_string("file.txt")?;
    Ok(())
}

// Custom error type
#[derive(Debug)]
enum AppError { Parse(ParseIntError), NotFound }

impl std::fmt::Display for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self { AppError::NotFound => write!(f, "not found"), ... }
    }
}

Traits

// Define a trait
trait Summary {
    fn summarize(&self) -> String;
    fn preview(&self) -> String { // default implementation
        format!("{}...", &self.summarize()[..20])
    }
}

// Implement a trait
impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

// Trait bounds — static dispatch
fn notify(item: &impl Summary) { println!("{}", item.summarize()); }
fn notify<T: Summary>(item: &T) { ... }           // equivalent
fn notify<T: Summary + Display>(item: &T) { ... } // multiple bounds

// where clause — cleaner for complex bounds
fn foo<T>(x: T) where T: Summary + Clone + std::fmt::Debug { ... }

// Trait objects — dynamic dispatch
fn notify(item: &dyn Summary) { ... }
let items: Vec<Box<dyn Summary>> = vec![Box::new(article)]

Generics

// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T { ... }

// Generic struct
struct Pair<T> { first: T, second: T }

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.first > self.second { println!("{}", self.first); }
    }
}

// Generic enum (built-in examples)
enum Option<T> { Some(T), None }
enum Result<T, E> { Ok(T), Err(E) }

// Monomorphization: Rust generates a specific version for each
// concrete type at compile time — zero runtime overhead

Lifetimes

// Explicit lifetime when compiler can't infer
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Struct holding a reference
struct Excerpt<'a> {
    text: &'a str,
}

// Lifetime elision rules (inferred automatically):
// 1. Each &param gets its own lifetime
// 2. If one &self/&mut self param, its lifetime = output lifetime
// 3. Otherwise you need explicit annotations

// 'static — lives for entire program
let s: &'static str = "I live forever";

Closures

// Basic closure
let double = |x| x * 2;
let add = |x, y| x + y;

// With type annotations
let multiply = |x: i32, y: i32| -> i32 { x * y };

// Closures capture their environment:
let factor = 3;
let triple = |x| x * factor;   // captures factor by reference

// move — take ownership of captured variables
let s = String::from("hi");
let greeting = move || println!("{}", s);  // owns s

// Closure traits:
// Fn     — borrows immutably (can call multiple times)
// FnMut  — borrows mutably (can call multiple times)
// FnOnce — takes ownership (can only call once)

fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
▶ Try it

Iterators

let v = vec![1, 2, 3, 4, 5, 6];

// Adapter methods (lazy — nothing runs until consumed)
v.iter()                          // &T
v.iter_mut()                      // &mut T
v.into_iter()                     // T (consumes vec)

// Common adapters
.map(|x| x * 2)
.filter(|&&x| x % 2 == 0)        // note: &i32 with iter()
.filter_map(|x| if x > 0 { Some(x) } else { None })
.flat_map(|x| vec![x, x * 2])
.take(3)  .skip(2)
.enumerate()                      // (index, value)
.zip(other_iter)                  // (a, b) pairs
.chain(other_iter)

// Consuming (terminal) methods
.collect::<Vec<_>>()
.sum::<i32>()
.product::<i32>()
.count()
.any(|&x| x > 3)
.all(|&x| x > 0)
.find(|&&x| x > 3)               // Option<&T>
.position(|&x| x == 3)           // Option<usize>
.max()  .min()
.fold(0, |acc, &x| acc + x)
.for_each(|x| println!("{}", x))
▶ Try it

String Types

// &str — immutable borrowed slice (usually what you want in params)
let s: &str = "hello";

// String — owned, heap-allocated, growable
let mut s = String::new();
let s = String::from("hello");
let s = "hello".to_string();
let s = "hello".to_owned();

// Conversion
let owned: String = s.to_string();   // &str → String
let slice: &str    = &owned;          // String → &str (free)
let slice: &str    = owned.as_str();  // explicit

// Building strings
s.push_str(" world");
s.push('!');
let combined = format!("{} {}", s1, s2);  // allocates
let combined = [s1, s2].join(" ");

// Useful methods
s.len()          s.is_empty()
s.contains("hi") s.starts_with("he")  s.ends_with("lo")
s.to_uppercase() s.to_lowercase()
s.trim()         s.trim_start()       s.trim_end()
s.replace("old", "new")
s.split(' ')     s.split_whitespace()
s.chars()        s.bytes()
s.parse::<i32>().unwrap()

Collections

// Vec<T>
let mut v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];
v.push(4);    v.pop();       // Option<T>
v.len()       v.is_empty()
v[0]          v.get(0)       // panics vs Option<&T>
v.contains(&3)
v.iter()      v.iter_mut()   v.into_iter()
v.sort()      v.sort_by(|a, b| a.cmp(b))
v.dedup()     v.retain(|&x| x > 0)
Vec::with_capacity(n)        // pre-allocate

// HashMap<K, V>
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("key", 42);
map.get("key")               // Option<&V>
map.contains_key("key")
map.remove("key")            // Option<V>
map.len()
// Insert-or-update idiom:
*map.entry("key").or_insert(0) += 1;
map.entry("key").or_insert_with(Vec::new).push(val);

// HashSet<T>
use std::collections::HashSet;
let mut set: HashSet<i32> = HashSet::new();
set.insert(1);  set.contains(&1);  set.remove(&1);
set.union(&other)  set.intersection(&other)

Modules & Visibility

mod utils {
    pub fn helper() {}         // public
    fn internal() {}           // private (module only)
    pub(crate) fn crate_fn() {} // visible within crate

    pub struct Config {
        pub name: String,      // field must also be pub
        secret: String,        // private field
    }
}

// Bring into scope
use utils::helper;
use std::collections::{HashMap, HashSet};
use std::io::{self, BufRead};   // self = io module itself
use std::fmt::Display as Fmt;   // rename with as

// Paths
utils::helper();        // absolute from crate root
super::parent_fn();     // parent module
self::local_fn();       // current module

Smart Pointers

// Box<T> — heap allocation, single owner
let b = Box::new(5);
println!("{}", *b);       // deref transparently

// For recursive types:
enum List { Cons(i32, Box<List>), Nil }

// Rc<T> — reference counted, single-threaded shared ownership
use std::rc::Rc;
let rc = Rc::new(5);
let rc2 = Rc::clone(&rc);   // cheap clone, increments count
Rc::strong_count(&rc);       // → 2

// Arc<T> — Rc but atomic (thread-safe)
use std::sync::Arc;
let arc = Arc::new(5);

// RefCell<T> — runtime borrow checking (interior mutability)
use std::cell::RefCell;
let cell = RefCell::new(5);
*cell.borrow_mut() += 1;    // mutable borrow at runtime

// Common combo: Arc<Mutex<T>> for shared mutable state
use std::sync::{Arc, Mutex};
let data = Arc::new(Mutex::new(0));
*data.lock().unwrap() += 1;

Concurrency

use std::thread;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;

// Spawn a thread — move ownership in
let handle = thread::spawn(move || {
    println!("in thread");
});
handle.join().unwrap();

// Channels — message passing
let (tx, rx) = mpsc::channel();
thread::spawn(move || { tx.send(42).unwrap(); });
println!("{}", rx.recv().unwrap());

// Shared state with Arc<Mutex<T>>
let counter = Arc::new(Mutex::new(0));
let c = Arc::clone(&counter);
thread::spawn(move || {
    *c.lock().unwrap() += 1;
}).join().unwrap();

// Send + Sync traits (auto-implemented)
// Send: safe to transfer ownership to another thread
// Sync: safe to share reference across threads

Async / Await

// Declare async function — returns a Future
async fn fetch() -> String {
    // .await suspends until the future is ready
    reqwest::get("https://api.example.com")
        .await.unwrap()
        .text()
        .await.unwrap()
}

// Runtime needed to drive futures (Tokio is most common)
#[tokio::main]
async fn main() {
    let data = fetch().await;
    println!("{}", data);
}

// Run concurrently (not in parallel by default)
let (a, b) = tokio::join!(task_a(), task_b());

// Spawn a task (like a thread but async)
let handle = tokio::spawn(async { heavy_work().await });
handle.await.unwrap();

Derive Macros

#[derive(
    Debug,      // {:?} and {:#?} formatting
    Clone,      // .clone()
    Copy,       // implicit copy on assignment (only for small types)
    PartialEq,  // == and !=
    Eq,         // full equality (requires PartialEq)
    PartialOrd, // < > <= >=
    Ord,        // full ordering (requires PartialOrd + Eq)
    Hash,       // use as HashMap key
    Default,    // Type::default() — zeros/empty
)]
struct Point { x: f64, y: f64 }

// serde (separate crate) — JSON/YAML/etc serialization
#[derive(serde::Serialize, serde::Deserialize)]
struct Config { name: String, port: u16 }

Common Traits

// Display — {} formatting (implement manually)
impl std::fmt::Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

// From / Into — type conversion
impl From<i32> for MyType {
    fn from(n: i32) -> Self { MyType(n) }
}
let x = MyType::from(5);
let x: MyType = 5.into();  // Into is auto-implemented from From

// Iterator — implement to make custom types iterable
impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<u32> { ... }
}

// Deref — dereference behaviour (*val)
// Drop — custom cleanup on drop
// AsRef — cheap reference conversion
🦀

准备好了吗?

26 个免费互动挑战。无需安装,无需注册,马上开始。