Rust (programming language)
≈ 11 min read
Names in other languages
No names in other languages are held for this entry yet.
Article bodies are English and are not machine-translated. A name in another language is not a translation of the article.
Rust is a general-purpose programming language that emphasizes performance, type safety, concurrency, and memory safety. Rust supports multiple programming paradigms. It was influenced by ideas from functional programming, including immutability, higher-order functions, algebraic data types, and pattern matching. It also supports object-oriented programming via structs, enums, traits, and methods. Rust enforces memory safety (i.e., that all references point to valid memory) without a conventional garbage collector; instead, memory safety errors and data races are prevented by the "borrow checker", which tracks the object lifetime of references at compile time. Software developer Graydon Hoare created Rust in 2006 while working at Mozilla, which officially sponsored the project in 2009. The first stable release, Rust 1.0, was published in May 20151. The project has been sponsored by the Rust Foundation since February 2021. Rust has been adopted by many software projects, especially web services and system software. It has been studied academically and has a growing community of developers.
Rust is a general-purpose programming language that emphasizes performance, type safety, concurrency, and memory safety. Rust supports multiple programming paradigms. It was influenced by ideas from functional programming, including immutability, higher-order functions, algebraic data types, and pattern matching. It also supports object-oriented programming via structs, enums, traits, and methods. Rust enforces memory safety (i.e., that all references point to valid memory) without a conventional garbage collector; instead, memory safety errors and data races are prevented by the "borrow checker", which tracks the object lifetime of references at compile time. Software developer Graydon Hoare created Rust in 2006 while working at Mozilla, which officially sponsored the project in 2009. The first stable release, Rust 1.0, was published in May 2015. The project has been sponsored by the Rust Foundation since February 2021. Rust has been adopted by many software projects, especially web services and system software. It has been studied academically and has a growing community of developers. fn main() { println!("Hello, World!"); } fn main() { let foo: i32 = 10; println!("The value of foo is {foo}"); } Variables are immutable by default, unless the keyword is added. The following example uses , which denotes the start of a comment. fn main() { // This code would not compile without adding "mut". let mut foo = 10; println!("The value of foo is {foo}"); foo = 20; println!("The value of foo is {foo}"); } Multiple expressions can define multiple variables with the same name, known as variable shadowing. Variable shadowing allows transforming variables without having to name the variables differently. The example below declares a new variable with the same name that is double the original value: fn main() { let foo = 10; // This will output "The value of foo is 10" println!("The value of foo is {foo}"); let foo = foo * 2; // This will output "The value of foo is 20" println!("The value of foo is {foo}"); } Variable shadowing is also possible for values of different types. For example, going from a string to its length: fn main() { let letters = "abc"; let letters = letters.len(); } fn main() { let x = { println!("this is inside the block"); 1 + 2 }; println!("1 + 2 = {x}"); } Trailing expressions of function bodies are used as the return value: fn add_two(x: i32) -> i32 { x + 2 } fn main() { let x = 10; if x > 5 { println!("value is greater than five"); } if x % 7 == 0 { println!("value is divisible by 7"); } else if x % 5 == 0 { println!("value is divisible by 5"); } else { println!("value is not divisible by 7 or 5"); } } and blocks can evaluate to a value, which can then be assigned to a variable: fn main() { let x = 10; let new_x = if x % 2 == 0 { x / 2 } else { 3 * x + 1 }; println!("{new_x}"); } fn main() { // Iterate over all integers from 4 to 10 let mut value = 4; while value fn main() { // Using `for` with range syntax for the same functionality as above // The syntax 4..=10 means the range from 4 to 10, up to and including 10. for value in 4..=10 { println!("value = {value}"); } } In the above code, is a value of type that implements the trait. The code within the curly braces is applied to each element returned by the iterator. Iterators can be combined with functions over iterators like , , and . For example, the following adds up all numbers between 1 and 100 that are multiples of 3: (1..=100).filter(|x| x % 3 == 0).sum() fn main() { let value = 456; let mut x = 1; let y = loop { x *= 10; if x > value { break x / 10; } }; println!("largest power of ten that is smaller than or equal to value: {y}"); let mut up = 1; 'outer: loop { let mut down = 120; loop { if up > 100 { break 'outer; } if down fn double(x: Option ) -> u64 { match x { Some(value) => value * 2, None => 0, } } Equivalently, this can be written with and : fn double(x: Option ) -> u64 { if let Some(value) = x { value * 2 } else { 0 } } let tuple: (u32, bool) = (3, true); let array: [i8; 5] = [1, 2, 3, 4, 5]; let value = tuple.1; // true let value = array[2]; // 3 Arrays can also be constructed through copying a single value a number of times: let array2: [char; 10] = [' '; 10]; fn print_string(s: String) { println!("{}", s); } fn main() { let s = String::from("Hello, World"); print_string(s); // s consumed by print_string // s has been moved, so cannot be used any more // another print_string(s); would result in a compile error } The function takes ownership over the value passed in; Alternatively, can be used to indicate a reference type (in ) and to create a reference (in ): fn print_string(s: &String) { println!("{}", s); } fn main() { let s = String::from("Hello, World"); print_string(&s); // s borrowed by print_string print_string(&s); // s has not been consumed; we can call the function many times } Because of these ownership rules, Rust types are known as affine types, meaning each value may be used at most once. This enforces a form of software fault isolation as the owner of a value is solely responsible for its correctness and deallocation. When a value goes out of scope, it is dropped by running its destructor. The destructor may be programmatically defined through implementing the trait. This helps manage resources such as file handles, network sockets, and locks, since when objects are dropped, the resources associated with them are closed or released automatically. fn main() { let mut x = 5; // ------------------+- Lifetime 'a // | let r = &x; // -+-- Lifetime 'b | // | | println!("r: {}", r); // -+ | // Since r is no longer used, | // its lifetime ends | let r2 = &mut x; // -+-- Lifetime 'c | } // ------------------+ The borrow checker in the Rust compiler then enforces that references are only used in the locations of the source code where the associated lifetime is valid. In the example above, storing a reference to variable in is valid, as variable has a longer lifetime ( ) than variable ( ). However, when has a shorter lifetime, the borrow checker would reject the program: fn main() { let r; // ------------------+- Lifetime 'a // | { // | let x = 5; // -+-- Lifetime 'b | r = &x; // ERROR: x does | | } // not live long -| | // enough | println!("r: {}", r); // | } // ------------------+ Since the lifetime of the referenced variable ( ) is shorter than the lifetime of the variable holding the reference ( ), the borrow checker errors, preventing from being used from outside its scope. Lifetimes can be indicated using explicit lifetime parameters on function arguments. For example, the following code specifies that the reference returned by the function has the same lifetime as (and not necessarily the same lifetime as ): fn remove_prefix (mut original: &'a str, prefix: &str) -> &'a str { if original.starts_with(prefix) { original = original[prefix.len()..]; } original } In the compiler, ownership and lifetimes work together to prevent memory safety issues such as dangling pointers. fn main() { let name1: Option = None; // In this case, nothing will be printed out if let Some(name) = name1 { println!("{name}"); } let name2: Option = Some("Matthew"); // In this case, the word "Matthew" will be printed out if let Some(name) = name2 { println!("{name}"); } } Similarly, Rust's result type Result holds either a successfully computed value (the Ok variant) or an error (the Err variant). Like Option , the use of Result means that the inner value cannot be used directly; programmers must use a match expression, syntactic sugar such as ? (the "try" operator), or an explicit unwrap assertion to access it. Both Option and Result are used throughout the standard library and are a fundamental part of Rust's explicit approach to handling errors and missing data. trait Zero: Sized { fn zero() -> Self; fn is_zero(&self) -> bool where Self: PartialEq, { self == &Zero::zero() } } impl Zero for u32 { fn zero() -> u32 { 0 } } impl Zero for f32 { fn zero() -> Self { 0.0 } } The example above includes a method that provides a default implementation that may be overridden when implementing the trait. // zero is a generic function with one type parameter, Num fn zero () -> Num { Num::zero() } fn main() { let a: u32 = zero(); let b: f32 = zero(); assert!(a.is_zero() && b.is_zero()); } In the examples above, as well as are trait bounds that constrain the type to only allow types that implement or . Within a trait or impl, refers to the type that the code is implementing. Generics can be used in functions to allow implementing a behavior for different types without repeating the same code (see bounded parametric polymorphism). Generic functions can be written in relation to other generics, without knowing the actual type. use std::fmt::Display; let v: Vec > = vec![ Box::new(3), Box::new(5.0), Box::new("hi"), ]; for x in v { println!("{x}"); } If an element in the list does not implement the Display trait, it will cause a compile-time error. macro_rules! sum { ( $initial:expr $(, $expr:expr )* $(,)? ) => { $initial $(+ $expr)* } } fn main() { let x = sum!(1, 2, 3); println!("{x}"); // prints 6 } In this example, the macro named sum is defined using the form macro_rules! sum { (...) => { ... } } . The first part inside the parentheses of the definition, the macro pattern ( $initial:expr $(, $expr:expr )* $(,)? ) specifies the structure of input it can take. Here, $initial:expr represents the first expression, while $(, $expr:expr )* means there can be zero or more additional comma-separated expressions after it. The trailing $(,)? allows the caller to optionally include a final comma without causing an error. The second part, after the arrow => , describes what code will be generated when the macro is invoked. In this case, $initial $(+ $expr)* means that the generated code will start with the first expression, followed by a + and each of the additional expressions in sequence. The * again means "repeat this pattern zero or more times". This means, when the macro is later called in line 8, as sum!(1, 2, 3) , the macro will resolve to 1 + 2 + 3 representing the addition of all of the passed expressions. [unsafe(no_mangle)] pub extern "C" fn exported_from_rust(x: i32) -> i32 { x + 1 } unsafe extern "C" { fn imported_into_rust(x: i32) -> i32; } The attribute enables deterministic memory layouts for s and s for use across FFI boundaries. External libraries such as and can generate Rust bindings for C/C++.
Text derived from https://en.wikipedia.org/w/index.php?oldid=1378876146 under CC BY-SA 4.0; modifications have been made.