A Trait in the Rust programming language enables what today’s coders commonly call “duck-typing” (walks like a duck and quacks like a duck).

In Rust, type refers to concrete types — the type of a value; whereas, a Trait refers to an abstract or generic type. Here the English word type lacks the specificity we need to describe these concepts, so we need adjectives to differentiate.

TLDR: traits vs types

The type keyword lets us define a type alias, like:

type Population = i32;

This is useful if I’m often passing around specific variables for a Population, and I have function that takes multiple numbers, then the compiler will be able to catch certain classes of errors:

fn report(p: Population, num: i32)

A Trait is a collection of functions that can be applied to a type (a built-in type, like i32 or a type that we have defined with a struct, enum or type alias). A good example of a Trait is ToString which is part of the Rust standard library:

pub trait ToString {
    fn to_string(&self) -> String;
}

Here’s a naive approach to implementing ToString on a custom struct:

struct Monster {
  eyeball_count: i32,
}

impl ToString for Monster {
  fn to_string(&self) -> String {
    format!("{}-eyed {}", self.eyeball_count, "monster")
  }
}

fn main() {
  let m = Monster { eyeball_count: 3 };
  println!("We just created a {}!", m.to_string())
}

Experienced Rustaceans would rarely implement the above code, instead they might implement std::fmt::Display which provides additional functionality we probably want, so if I write this instead of impl ToString for Monster:

impl fmt::Display for Monster {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "{}-eyed {}", self.eyeball_count, "monster")
  }
}

Then our code that calls to_string will still work, but we can also provide the variable directly to println! (or format!) and it will also work:

  println!("We just created a {}!", m);

This is because Rust allows for implementing functions on Traits in terms of a generic type.

Traits in context

Traits help break down what’s needed and make our code reusable and easier to maintain. Most libraries will use many Traits from the std libary, as well as their own Traits, which can make the learning curve a bit steep, but very flexible once you understand what they do.

So, as I’m learning, it helps me to spell everything out, but then I end up with code that was a bit hard to read. Check out this beauty that I wrote yesterday:

async fn read_some<R: AsyncRead + Send + Unpin>(mut reader: R) -> Result<(), std::io::Error>

I’m using Rust 1.39 with built-in async fn which means that the compiler will build me a future and I can declare as the return value to be whatever type that Future will ultimately produce (or simply what my function returns). In this case, I want to be able to pass in a tokio::net::TcpStream and also a “ReadHalf” that is returned from split.

My first attempt at refactoring was to do this:

type Reader = AsyncRead + Send + Unpin;

The above code doesn’t do what I wanted. I’ve explained above that type creates an alias for a concrete type; however, when we provide Traits, it (unexpected for me) creates a “Trait object” which is not the abstract type I was looking for. What I wanted to do was to define a new Trait that composes the other traits, but has no implementation of its own. Here’s the syntax I was looking for:

trait Reader: AsyncRead + Send + Unpin { } 
impl<T: AsyncRead + Send + Unpin> Reader for T {}

which I can then use like this:

async fn read_some<R: Reader>(mut reader: R) -> Result<(), std::io::Error>

or in a slightly more readable form with where:

async fn read_some<R>(mut reader: R) -> Result<(), std::io::Error>
where R: AsyncReader  

If you want to see the working code, I have a few examples here: https://github.com/ultrasaurus/rust-tokio-proxy where each can be executed like this: cargo run --example reader-type


Many thanks to the Alice, David, Lucio and Jeb on Tokio discord who helped me understand types, traits and how they are used in tokio!

I want to write a library in Rust that can be called from C and just as easily called from Rust code. The tooling makes it pretty easy, but I had to look in a few places to figure how it is supposed to work and get tests running in both languages.

C library

To focus on the process of building and testing, the library will have a single function that adds two numbers. I wrote it in pure C first:

lib.c

int add(int a, int b) {
  return a + b;
}

lib.h

int add(int a, int b);

main.c

#include <stdio.h>
#include "lib.h"

int main() {
    int sum = add(1,2);
    printf("1 + 2 = %d\n", sum);
}

one-liner to compile and run the app

gcc *.c -o app && ./app

output: 1 + 2 = 3

then I wrote a simple automated test, based on tdd blog post

Rust library

cargo new add --lib

replace lib.rs with

#[no_mangle]
pub extern "C" fn add(a: i32, b:i32) -> i32 {
    a + b
}


#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        use crate::add;
        assert_eq!(add(2,2), 4);
    }
}

build and run with cargo test
which should have output like

$ cargo test
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running target/debug/deps/add-45abb08ccefdc53c

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests add

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Compile as static library

Add to Cargo.toml

[lib]
name = "add"
crate-type = ["staticlib"]  

Now cargo build generates a compiled library file: target/debug/libadd.a. I could have stopped there, but I expect to iterate on this a bit and I had read about a crate that generates the C header file…

Generate a header (command-line)

First, install the lovely cbindgen crate (using –force just to make sure everything is up to date with the latest):

cargo install --force cbindgen

the command-line tool is pretty neat:

touch cbindgen.toml    # can be empty for defaults
cbindgen --config cbindgen.toml --crate add --output add.h

The above command will generate “add.h” file at the root of the crate.

Generate a header (cargo build)

I prefer to have the header generation integrated with cargo build (or at least I think I will). Here are the steps:

Add to Cargo.toml:

[build-dependencies]
cbindgen = "0.12"

By default, the header file is buried in target/debug/build/add-... with a bunch of intermediary build files. I find that it is nice to put it at the root of my crate where it is easy to find. Below is a custom build file that puts it in the crate root (aka CARGO_MANIFEST_DIR, the directory that Cargo.toml is in).

build.rs:

extern crate cbindgen;

use std::env;
use std::path::PathBuf;


fn main() {
  let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")
        .expect("CARGO_MANIFEST_DIR env var is not defined"));

  let config = cbindgen::Config::from_file("cbindgen.toml")
        .expect("Unable to find cbindgen.toml configuration file");

  cbindgen::generate_with_config(&crate_dir, config)
        .expect("Unable to generate bindings")
        .write_to_file(crate_dir.join("add.h"));
}

As mentioned above, cbindgen.toml may be empty, but here’s some settings I like:

include_guard = "add_h"
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
language = "C"
includes = []
sys_includes = ["stdint.h"]
no_includes = true   

Confusingly no_includes means no extra includes. I prefer to have only the ones that I know are needed, rather than some random list of “common” headers.

Here’s my generated header:

#ifndef add_h
#define add_h

/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */

#include <stdint.h>

int32_t add(int32_t a, int32_t b);

#endif /* add_h */

Putting it all together

Example main.c in the root of the crate:

#include <stdio.h>
#include "add.h"

int main() {
    int sum = add(1,2);
    printf("1 + 2 = %d\n", sum);
}

compile and run:

gcc main.c add/target/debug/libadd.a -o app && ./app

outputs: 1 + 2 = 3

This make me unreasonably happy. Rust syntax can be a bit finicky and certainly takes a bit of getting used, but this kind of tooling could more than make up for that in accelerating the dev cycle.

For the full applications with a mini test suite in C, see github/ultrasaurus/rust-clib.

Running an app on heroku requires at least one entry point responding to http. An easy way to do this is to use hyper to create a simple web service.

Setup

cargo new hello_rust --bin
cd hello_rust
git init
git add .
git commit -m "cargo new hello_rust --bin"

add to Cargo.toml:

[dependencies]
hyper = "0.13"

Web service code

The core code to set up a little web service has a few key parts:

  1. the service (async fn hello)
    • an async function that takes a hyper::Request and returns a hyper::Response in the Result
    • Request is generic over the Body, so it seems nifty to be able to provide our own Rust types for specific content (like JSON) and also for validating API POST params
    • Result is Infallible: a Rust error type signifying that the function never returns an error
  2. make_service_fn — docs are a bit sparse on this, but I think all it does it generate an instance of the service with the Request context that so that each request can run concurrently
  3. Server::bind(&addr).serve(…) — Hyper uses a builder pattern where Server::bind generates a Builder, where you can configure http1/2 support and then a running instance of the Server is created by calling the serve method with the service function.

To see this in action, replace main.rs with this code:

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

async fn hello(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from(
        "<HTML><H1>Hello World!</H1><HTML>",
    )))
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

    let make_svc = make_service_fn(|_conn| {
        async { Ok::<_, Infallible>(service_fn(hello)) }
    });

    let addr = ([0, 0, 0, 0], 3000).into();

    let server = Server::bind(&addr).serve(make_svc);

    println!("Listening on {}", addr);

    server.await?;

    Ok(())
}

to run the app locally:

cargo run

then in the browser, go to http://localhost:3000/

Heroku setup

1. Heroku CLI

Install heroku CLI or if you already have it:

heroku update

Then to set up the app on heroku:

heroku create --buildpack emk/rust

2. Procfile

Add a Procfile for heroku to know what to call when it receives a web request:

echo "web: ./target/release/hello_rust" >> Procfile

3. Port configuration

Heroku requires that we listen on the port specified with the PORT env var. So, add the following code and replace the hard-coded port number with this variable:

    let port = env::var("PORT")
        .unwrap_or_else(|_| "3000".to_string())
        .parse()
        .expect("PORT must be a number");

4. Deploy!

Deploy the app by pushing code to the Heroku remote repository that was set up by the CLI in step 1.

 git push heroku master

Full code for the app is on github.com/ultrasaurus/hello-heroku-rust

Background

My environment info (rustup show):

stable-x86_64-apple-darwin (default)
rustc 1.39.0 (4560ea788 2019-11-04)