The mian function represents the entry point to the program. The main function is necessary for binary executables, but not for library functions.
The function definitions
Functions in Rust are defined by FN. Code sample
pub fn fizz_buzz(num: i32) -> String { if num % 15 == 0 { return "fizzbuzz".to_string(); } else if num % 3 == 0 { return "fizz".to_string(); } else if num % 5 == 0 { return "buzz".to_string(); } else { return num.to_string(); } } fn main () { assert_eq! (fizz_buzz(15), "fizzbuzz".to_string()); assert_eq! (fizz_buzz(3), "fizzbuzz".to_string()); assert_eq! (fizz_buzz(5), "buzz".to_string()); assert_eq! (fizz_buzz(13), "13".to_string()); }Copy the code
Pub fn fizz_buzz(num: i32) -> String Pub fn fizz_buzz(num: i32) -> String pub fn fizz_buzz(num: i32) -> String The Rust compiler strictly adheres to this type of contract, and compiles an error if the incoming or returned type is incorrect.
Scope and life cycle
The Scope of Rust is static, or Lexocal, defined by a pair of curly braces at the lexical analysis stage and does not change dynamically. The sample is as follows
fn main () { let v = "hello world!" ; assert_eq! (v, "hello world!" ); let v = "hello Rust!" ; assert_eq! (v, "hello Rust!" ); { let v = "Hello World!" assert_eq! (v, "Hello World!" ); } assert_eq! (v, "hello Rust!" ); }Copy the code
First declare the v variable and assign it to Hello World! , the assertion verifies its value, and again declares the variable binding v with a value of Hello Rust! This continuous definition of variables with the same name is called Variable Shadow, and the final value of Variable V is determined by the second Variable definition.
A function pointer
In Rust, functions are first-class citizens, meaning that functions themselves can be used as arguments and return values to functions. Example:
fn sum(a: i32, b: i32) -> i32 { a + b } fn product(a: i32, b: i32) -> i32 { a * b } fn main () { let a = 2; let b = 3; assert_eq! (math(sum, a, b), 5); assert_eq! (math(product, a, b), 6); }Copy the code
In main, the math function is called twice, passing sum and product as arguments, respectively. Sum and product are two functions used for summation and product. They are of type fn (i32, i32) -> i32, respectively, so they can be passed as arguments to the Math function. We use the name of the function directly as the pointer to the function.
fn is_true -> bool {true} fn true_maker() -> fn() -> bool{is_true} fn main() { assert_eq! (true_maker()(), true); }Copy the code
Function true_maker (fn() -> bool); function true_maker (fn() -> bool); In the assertion of the main function, the true_maker()() call is equivalent to (true_maker())() calling true_maker() first, which returns the is_true function pointer, and then is_true(), which returns true.
CTFE mechanism
Rust compilers can also have compile-time Function Execution (CTFE) capabilities like C++ or D. The example is const fn
/ / #! [feature(const_fn)] const fn init_len() -> usize { return 5; } fn main () { let arr = [0, init_len()]; }Copy the code
This code uses const fn to define the function init_len, which returns a fixed value of 5. In main, an array of length N with an initial value of 0 is initialized by [0; N], where N is evaluated by calling init_len. The Rust fixed length array must know its length at compile time or it will get compiled wrong, so init_len must be evaluated at compile time. That’s what CTFE is capable of. Functions defined using const fn must be determinable without ambiguity. Unlike functions defined by fn, const fn forces the compiler to execute the function at compile time. The const keyword is typically used to define global variables.
In addition to const FN, we are officially implementing the const Generics feature. Impl
Foo for[T;N] {… } to implement trait Foo for arrays of all lengths. Makes the experience of using arrays much better.
CTFE in Rust is performed by mirI. Miri is a MIR interpreter that has been integrated into the Rust compiler RUSTC. The Rust compiler currently supports constants such as literals, primitives, arrays, field structures, enumerations, block expressions containing only one line of code, ranges, and more. Rust still has a lot of work to do before it has full CTFE support.
closure
Closures, also known as anonymous functions, have several characteristics: * They can be called just like functions. * You can barge free variables in the context. * You can automatically infer the type of input and return.
fn main () { let out = 42; // fn add (i: i32, j: i32) -> i32 {i + j + out} fn add (i: i32, j: i32) -> i32 { i + j} let closure_annotated = |i: i32, j: i32| -> i32 {i + j + out} let closure_inferred = |i, j| i + j + out; let i = 1; let j = 2; assert_eq! (3, add(i, j)); assert_eq! (45, closure_annotated(i, j)); assert_eq! (45, closure_inferred(i, j)); }Copy the code
In the code above, the main function defines another function, add, and two closures, closure_annotated and closure_inferred. Closure calls and function calls are desirable, but one important difference between closure composite functions is that closures can capture external variables while functions cannot. This is very different from JavaScript. JavaScript functions can get external variables themselves, but they don’t have to be closures. Closures can also be used as function parameters and return values, but they are used slightly differently.
fn closure_math<F: Fn() -> i32>(op: F) -> i32 { op() } fn main () { let a = 2; let b = 3; assert_eq! (closure_math(|| a + b), 5); assert_eq! (closure_math(|| a * b), 6); }Copy the code
In the above code, we define the function closure_math, whose argument is a generic F, and the qualification of the generic Fn() -> i32 trait, which means that the function only allows as arguments the type that implements Fn() -> i32 trait. Closures in Rust are actually implemented as a combination of an anonymous construct and a trait. So, in the main function call math function, were introduced to | | a + b and | | a * b can achieve Fn () – > i32. Inside the Math function, the incoming closure is called directly.
Closure as the return value
fn tow_times_impl() -> impl Fn(i32) -> i32 { let i = 2; move |j| j * i } fn main () { let result = tow_times_impl(); assert_eq! (result(2), 4); }Copy the code
In the above code, impl Fn(i32) -> i32 is used as the return value of the function, which means that the implementation of Fn(i32) -> i32 type is not known in the function definition, but when the function is called, the compiler will infer the return type. This process is cost-free and abstract, and everything happens at compile time.
Note that the two_times_IMPl uses the move keyword when the closure is finally returned, because normally closures capture variables by reference by default, and if the closure is returned, the reference is returned, but the local variable I within the function is destroyed after the entire function is called. The closure returns a reference to the variable I, which becomes a dangling pointer. Rust is a memory-safe language, and the compiler will report an error if the move keyword is not applied. Using the move keyword, you transfer ownership of the captured variable I to the closure so that the variable is not captured by reference and the closure can be safely returned.
# Rust learning