What is Rust all about

According to our quick survey, most of you have decent knowledge of some programming language, very often C++. What makes Rust different is its type system and commitment to memory and thread safety.

Basic usage of Rust

We discussed the specifics of Rust over a few iterations of code that converged to the following simple example:

fn init_list(list: &mut Vec<String>) {
    list.push("gamma".to_string());
}

fn main() {
    let mut list: Vec<String> = vec![
        "alpha".to_string(),
        "beta".to_string(),
    ];

    init_list(&mut list);

    for number in list {
        println!("{}", number);
    }
}

Try it yourself:

  • Locally using standard tools
    • Create using cargo new example
    • Edit src/main.rs
    • Run using cargo run
  • Locally using cargo-script
  • Using online tools
    • Rust Playground
    • Compiler Explorer
    • Replit

Takeaways:

  • It is relatively easy to express one’s intentions in Rust. The language tries to be as simple and useful as possible within its constraints.
  • No easy at all to appease Rust’s type system and make the code actually compile and run.
  • Knowledge of C++ helps to grasp the theory quicky but may lead to ideas that are hard to make right.

Interesting details:

  • Immutability/constantness is the default. Mutability is explicit both in function signitures and in function calls.
  • Type conversions are explicit. String literals and stored string differ in type and must also be converted explicitly.
  • Passing by value transfers ownership. Programs that try to use a moved value do not compile.

A more useful example

Rust is designed to be a practical language. Most users are supposed to combine their own code with third party libraries and frameworks in addition to the Rust standard library.

We discussed an example that turns a Rust function into a web application:

use hyper::{Body, Request, Response};
use hyper::service::{make_service_fn, service_fn};
use hyper::server::Server;
use hyper::server::conn::AddrStream;
use std::convert::Infallible;

#[tokio::main]
async fn main() {
    // Note: You definitely do not need to understand this code yet. It is a glue between the
    // hello() function and the HTTP library.
    let make_svc = make_service_fn(|_socket: &AddrStream| async {
        Ok::<_, Infallible>(service_fn(hello))
    });

    let address = ([127, 0, 0, 1], 3000).into();
    let server = Server::bind(&address).serve(make_svc);

    server.await.unwrap();
}

async fn hello(_request: Request<Body>) -> Result<Response<Body>, Infallible> {
    let text = "Hello World!".to_string();
    let body = Body::from(text);
    let response = Response::builder()
        .body(body)
        .unwrap();
    Ok(response)
}

The code uses external dependencies:

# Cargo.toml
[dependencies]
hyper = { version = "0.14.13", features = ["full"] }
tokio = { version = "1.12.0", features = ["full"] }

Takeaways:

  • We use hyper for relatively low-level web server/application development together with tokio for concurrency.
  • All functions are turned into asynchronous coroutines and passed to the libraries.
  • We can build the response from pieces one of which is a Body containing the text (HTML) output for the browser.

Interesting details:

  • The “magic” code in main() creates a handler that in turn creates a request handler for each connection that just wraps hello().
  • Functions have strict types too. The type of the anonymous “magic” function passed to make_service_fn() is complex but we let the compiler handle it.