kota's memex
fn main() {
    plus_one(5);
}

fn another_function(x: i32) -> i32 {
    x + 1
}

statements vs expressions

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.