fn main() {
plus_one(5);
}
fn another_function(x: i32) -> i32 {
x + 1
}
statements vs expressions
- Statements are instructions that perform some action and do not return a value.
- Expressions evaluate to a resultant value.
Creating a variable and assigning a value to it is a statement:
fn main() {
let y = 6;
}
Unlike in other languages, since variable assignment is strictly a statement, you cannot do the following as you might in C or Ruby:
let x = (let y = 6);
Declaring a function is a statement, but calling a function (or macro) is an expression.
passing values
The mechanics of passing a value to a function are similar to those when assigning a value to a variable. Passing a variable to a function with move or copy, just as assignment does:
fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it's okay to still
// use x afterward
} // Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.
fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.
fn makes_copy(some_integer: i32) { // some_integer comes into scope
println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.
return values
Like other languages functions can return values to the code that calls them. We
don't name the return values, but we must declare their type with ->:
fn five() -> i32 {
5
}
Note the lack of semicolon and return keyword above. If a semicolon is used you must type the return keyword which is normal practice for any function more than a line or two.
giving ownership
Returning values can also transfer ownership:
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.
fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it
let some_string = String::from("yours"); // some_string comes into scope
some_string // some_string is returned and
// moves out to the calling
// function
}
// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope
a_string // a_string is returned and moves out to the calling function
}
multiple return values
We can use tuples (kinda like in go) to return multiple values from a function:
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{s2}' is {len}.");
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() returns the length of a String
(s, length)
}
references
In the above example we passed a string into and out of a function which was a bit too much ceremony and quite clunky. Normally, you would just pass a "reference". A reference is like a pointer in that it's an address we can follow to access the data stored at that address; that data is owned by some other variable. Unlike a pointer, a reference is guaranteed to point to a valid value of a particular type for the life of that reference.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{s1}' is {len}.");
}
fn calculate_length(s: &String) -> usize {
s.len()
}
associated functions
let mut guess = String::new();
The :: syntax in ::new indicates that new is an associated function on
the type String. This new function creates a new, empty string. This is a
common idiom for initializers.
methods
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
Calling io::stdin() returns a type std::io::Stdin, which represents a handle
to the standard input of a terminal. The .read_line(&mut guess) function is a
method of that type.
This method itself returns a type io::Result which is an enum with two
variants: Ok and Err. This result type has a method called .expect which
we use to crash the program with an error message. If you do not use the
.expect method the compiler will show a warning. More complex error handling
than simply crashing is of course possible.
naming
By convention functions and variables use snake_case in rust.