A guide to Rust for Kotlin developers

This guide should help developers experienced with Kotlin quickly and easily learn the basics of Rust by comparing the major differences but also how the languages, by the nature of them both being modern languages, have quite a few similar features.

This book is a summary of the official Rust books, as well as forum threads, GitHub issues, and StackOverflow posts I've read and testing I've done. Please also read the following books for a much more complete understanding of Rust:

The official docs (often called the rust book or just the book in discussions)Link
A collection of examples for common patterns and tasksLink
Guide for Cargo (see also the Cargo chapter)Link

A lot of things you already know are going to be re-explained in this guide for two reasons:

  1. as a refresher and just to make your understanding is correct
  2. to help explain and draw out differences between the languages; Rust is designed so that the compiler can guarantee some level of correctness in regards to variable access and destruction, and as such it will not compile code it can't predict.

Some of the code samples have Ferris on them:

FerrisMeaning
This code does not compile!
This code panics!
This code block contains unsafe code.
This code does not produce the desired behavior.

(taken from the official book)

Basics

To start with Rust you'll the compiler, go to rustup.rs and follow the instructions.

Then I recommend installing and using Intellij CLion as you're already familiar with Intellij IDEs, otherwise you can use VSCode.

Command line basics

Rust is controlled by it's package manager cargo

To run your program execute cargo run

To test it's cargo test

Syntax differences

In Rust...

  • lines have to end with a semicolon, except if it's an implicit return value (like in Kotlin lambdas).
  • methods parameters can not be variadic, named or have default values.
  • method overloading is not supported.
  • the naming scheme is snake_case for methods, variables and files; and CamelCase for Traits, Structs, Impls and Enums.
  • annotations are written like #[Example] instead of @Example

Macros

Rust uses macros a lot as they provide ways to shorten code, and provide variadic and optional parameters, and also overloaded methods. Macros are invoked by the macro name then an exclamation mark, i.e. example!.

Macros can be used like functions example!() but anything can follow the exclamation mark as macros are very powerful, for example:

println!("Hello World");

let list = vec![1, 2, 3];

let foo = dsl! {
	init_with_default
	config = bar
}

//At compile time this will invoke the example macro with Foo as the parameter
#[Example]
struct Foo {

}

The annotation macros will generate code for Structs like in Kotlin.

Macros can generate Rust code at compile time or run and return a result at runtime like a method.

Most programs don't need their own macros but will often use ones from std or crates.

Primitives

In Kotlin there are few commonly used primitive types:

NameBitsType
Byte8Integer
Int32Integer
Long64Integer
Float32Floating Point
Double64Floating Point
Char16UTF-16 Character
BooleanN/ABoolean

In Kotlin the Integer types are signed but there unsigned versions, i.e UByte.

Rust uses a prefix followed by the bit size to create a number type (e.g. i32 meaning a 32 bit signed integer), these are the prefixes:

CharacterType
iSigned Integer
uUnsigned Integer
fFloating Point

Floating points are 32 and 64 bit only but integers can be 8, 16, 32, 64, and 128 bits; there are also two architecture dependent sizes: isize and usize these are whatever the pointer size is for the CPU (for most new computers and phones that is 64 bits). As an example the equivalent to a Kotlin Int is i32 and a Double is f64.

usize is important as it's the primary number type: it's used in array, list and string formatting indexing and lengths/sizes of objects.

Suffixes for literals exist in Rust like in Kotlin but rather being for some types, suffixes exist for all primitives and they are the name of type, e.g. 3_i32 or 10.3f32, the suffixes are only necessary if the compiler can not infer the type or if you want to specify it; also floating point numbers can be written without the 0, e.g. 1.

Booleans are the same in both languages except it's bool instead of Boolean.

Characters are different though, char in Rust is 4 bytes and can represent any Unicode scalar value. If you need to do text manipulation and need to handle any character outside of ASCII you will probably need to use these crates:

NameDescription
unicode-segmentationUsed to iterate over grapheme clusters
unicode-normalizationConverts char + modifier into single character, this is vital if you need to compare Unicode strings

See also the Strings chapter for more information about how unicode is handled in Rust.

Error Handling

Rust has a generic error interface: std::error::Error, it's optionally may have a source error and/or backtrace. Instead of exceptions and try..catch the follow is used:

Kotlin

fun main() {
  try {
    openSocket()
    println("Worked")
  } catch (e: IOException) {
    println("Error: " + e.message)
  }
}

Rust

use std::error::Error as StdError;
type Error = Box<dyn StdError>;

fn main() {
	match open_socket() {
		Ok(()) => println!("Worked"),
		Err(e) => println!("Error: {}", e)
	}
}

fn open_socket() -> Result<(), Error> { Ok(()) }

You can call unwrap() on a Result to get the success value or crash the app. To avoid repetitive code you can use ? which will immediately return the error:

fn unwrap_example() {
	let foo = open().unwrap();
}

//the question mark operator is only valid in methods that return Result
fn better_example() -> Result<(), Error> { 
	let bar = open()?;

	Ok(())
}

Some crates errors aren't compatible with each other and so have to be converted to something before the error can be returned:

fn main() -> Result<(), Error> { 
	let file = open_file()?; //Error type is IoError
	let socket = open_socket()?; //Error type is NetError

	Ok(())
}

This example wouldn't compile because the size in memory of a return type must be known at compile time and Error is a trait (an interface in Kotlin). So it must be wrapped like so Box<dyn Error>, Box moves the value to the heap and is essentially a pointer, dyn just means the type is dynamically dispatched (i.e. a trait). (see https://doc.rust-lang.org/std/keyword.dyn.html)

  1. Create a wrapper type and implement it for all error types that you have to handle
  2. Use eyre

I highly recommend anyhow for all apps, it automatically handles all error types and reduces boilerplate:

use eyre::Result; //import eyre Result and Error instead of std as needed

fn main() -> Result<()> { //Result only has single parameter
	Ok(())
}

Types and variables

Mutability

Because of the limitations imposed by the JVM in Kotlin variables are mutable or immutable sometimes based on type and sometimes by declaration. In Rust (and some other languages such as Swift) mutability is controlled via declaration:

fn main() { 
	let foo: i32 = 1;     //immutable
	let mut bar: i32 = 2; //mutable
}

Immutable variables can't be referenced as mutable, so the following will not compile:

fn main() {
	let foo: i32 = 1; 

	//This is invalid as the variable is immutable and so all 
	//references must be immutable as well
	change_ref(&mut foo); 

	//This is fine as a copy of the value is passed into the 
	//method and only the copy is mutable
	change_val(foo); 
}

fn change_ref(value: &mut i32) {
	*value += 1; 
}

fn change_val(value: mut i32) {
	value += 1;
}

Note that unlike in Kotlin lists (and any other objects and their properties) in Rust can't be changed if the variable isn't marked as mutable, so the following will not compile:

fn main() {
	let list = vec![1, 2, 3];

	list.push(4);
}

See also Interior mutability

Strings

In both Kotlin and Java, essentially, there is just one String type: String. Whether the text is hardcoded, from a file or user input the same class is used. Rust has two String types: String and str. A hardcoded string will be of type &'static str and a string read from anywhere else maybe a String or &str depending on what the method returns. They can be converted between themselves, in most circumstances.

String is a pointer to a string in heap with a capacity, this means it can grow and can be mutable. A &str is a char array and so has a fixed length. If you attempt to slice a string so it would cause a Unicode character to be broken Rust will panic. Use the .chars() method to access each character independently. So to get the length of a string in bytes you use foo.len() and to get the number of characters you use foo.chars().count().

Note that std Rust has limited supported for unicode and you may need to use crates to add missing features, this is because unicode changes regularly and the unicode table is quite large and has to be compiled into your program.

NameDescription
unicode-segmentationUsed to iterate over grapheme clusters
unicode-normalizationConverts char + modifier into single character, this is vital if you need to compare Unicode strings

Common types

Lists

KotlinRust
TypeList<T>, MutableList<T>, ArrayList<T>Vec<T>
ConstructorArrayList(size), ArrayList(collection)Vec::new(), Vec::with_capacity(size)
ShorthandlistOf(items...), mutableListOf(items...), arrayListOf(items...)vec![size; default], vec![items...]

Maps

KotlinRust
TypeMap<K, V>, MutableMap<K, V>, HashMap<K, V>HashMap<K, V>
ConstructorHashMap(size), HashMap(map)HashMap::new()
ShorthandmapOf(items...), mutableMapOf(items...), hashMapOf(items...)N/A [^1]

Tuples

KotlinRust
TypePair<T1, T2>, Triple<T1, T2, T3>(T1, T2, ...)
ConstructorPair(value1, value2), Triple(value1, value2, value3)N/A
Shorthandvalue1 to value2(value1, value2, ...)

Arrays

KotlinRust
TypeArray<T>[T]
ConstructorArray(size, builder_method)N/A
ShorthandarrayOf(items...)[items...]

[^1] Crates such as maplit do provide macros for this.

Constants

In Rust there are two types of constants const and static. const are immutable values hardcoded into the program. statics are optionally mutable values that are globally available. Using mutable statics is unsafe unless wrapped in thread safe structs such as Arc and Mutex. (See Concurrency)

#![allow(unused)]
fn main() {
const VERSION: u32 = 1;
static PROGRAM_NAME: &'static str = "Example";
}

Currently const and static values must be known at compile time. To get around this you can use the lazy_static crate, it works like by lazy {} in Kotlin:

use lazy_static::lazy_static;

lazy_static! {
	static ref A_MAP = HashMap::new();
}

const values are inserted at compile time where ever they were used, and so if you make a a mutable constant every use will be it's own instance.

A method can be constant if it's all of it's internal are constant as well, for example:

#![allow(unused)]
fn main() {
const FOO: usize = 1;
const BAR: usize = 2;

const RESULT: usize = add(FOO, BAR);

const fn add(lhs: usize, rhs: usize) -> usize {
	lhs + rhs
}
}

References

All variables can be passed as a reference by prefixing with a &, for example:

fn main() {
	let foo = 10; 
	print(foo); 
	print_ref(&foo);
}

fn print(value: i32) { 
	//Passed by value so no need to deference
	example(value);
}

fn print_ref(value: &i32) {
	//Dereferenced with *
	example(*value); 
}

fn example(value: i32) {
	//do something with value
}

Dereferencing a variable moves the value, so the value must implement Copy. See Deriving and implementing

For clarity:

SymbolsMeaning
<No symbols>Value, immutable
mutValue, mutable
&Reference, immutable
&mutReference, mutable
*Dereferenced

Borrowing and Ownership

In Kotlin a variable exists, and is available, while in it’s scope. A global static variable is always available but a variable created in a method (unless returned) only exists during that instance of the methods execution. Rust is basically the same and generally you’ll be able to write code without having to think about the borrowing system, but sometimes you will have to deal with it.

This will not compile as bar has taken ownership of the data in foo and so foo can no longer be used:

let foo = String::from("Hello"); 
let bar = foo;
println!("{}", foo);

This will compile however as numbers have Copy implemented for them and so num2 automatically makes a copy of num1s data:

#![allow(unused)]
fn main() {
let num1 = 54;
let num2 = num1; 
println!("{}", num1);
}

but this can be replicated for the string example by doing:

#![allow(unused)]
fn main() {
let foo = String::from("Hello"); 
let bar = foo.clone();
println!("{}", foo);
}

This will only work when the type implemented Clone. Not all types support Clone as it may be impossible to copy it’s data, for example with network streams. Ownership and borrowing apply to all methods:

fn main() {
	let a = String::from("Hello"); 
	let b = return_param(a);
	let c = length(b);
    println!("{}", c);
}

fn return_param(param: String) -> String { 
	return param;
}

fn length(param: String) -> usize {
	return param.len();
}

When main is executed both a and b are lost, length takes ownership of the string and it is dropped at the end of length, to keep b in memory either of the following changes could be made:

fn main() {
let str = String::from("test");
let _ref_len = length_ref(&str);
let (nstr, len) = length(str);
println!("({},{})", nstr, len);
}

fn length_ref(param: &String) -> usize {
	return param.len(); //not dereferenced:
	//because param is a reference len() will return it's result as a reference
	//and because usize implements Copy it will be automatically dereferenced
}

//or

fn length(param: String) -> (String, usize) { 
	let len = param.len();
	return (param, len);
}

References are just pointers and so don’t take ownership but instead the value is borrowed, there are some rules around this for example only one mutable reference can exist at once. Because of this a potential helpful way to think about this is shared vs unique, you can as many read only references as you want shared around but when writeable only a single unique reference can exist (to avoid race conditions, etc).

Rust supports generics like Kotlin and they are expressed like this: Vec<Item>, occasionally you might see Vec<&'a Item> the 'a is a lifetime notation and these are used to guide the compiler as to how long references will be alive. The lifetime name doesn’t matter but the standard names are 'a, 'b and 'c, except for 'static which means the variable must always be available, i.e. a hardcoded value.

If a parameter has the lifetime a and a result also has the lifetime a like this:

#![allow(unused)]
fn main() {
struct Foo {
	contents: String
}

impl Foo {
	//like with generics the lifetime has to specified in advance with <>
	fn get_first<'a>(&'a self) -> &'a str {
		&self.contents[0..1]
	}
}
}

then this is saying the instance of Foo must live as long as the reference returned by get_first.

However, in the example above the lifetimes aren't because the method has one parameter that is a reference and one result that is a reference and they share the same lifetime, so Rust will automatically assume the lifetime.

This would not compile:

struct Foo {	contents: String }

impl Foo {
fn get_first<'a>(&'a self) -> &'a str {&self.contents[0..1]}
}

fn main() {
	let result = get_char();
}

fn get_char() -> &str {
	let foo = Foo { contents: String::from("example") };
	let chr = foo.get_first();
	return chr;
}

as there's no lifetime on the result of get_char and there can't be as foo is dropped at the end of get_char and so chr would be pointing to an invalid section of memory.

Classes, or the lack there of

In Kotlin has Classes, Abstract Classes, Interfaces, and extension methods. Rust has Traits, Structs, and Impls.

Kotlin

  • An interface can have methods but can not have variables with values, and may extend another Interface. It can be supplemented with extension methods or sub classed by other Classes, Abstract Classes or Interfaces.
  • A class can have variables and methods, and may extend a Class, an Abstract Class and/or Interface(s). It can be supplemented with extension methods or sub classed by other Classes or Abstract Classes.
  • An abstract class can have variables and methods, and may extend a Class, an Abstract Class and/or Interface(s). It can be supplemented with extension methods or sub classed by other Classes or Abstract Classes.

Rust

  • A trait is like an interface, it defines a list of methods that must be implemented. It can extend other traits, although this is rare.
  • A struct is the closest thing to a class but although it has a list of variables it does not have any methods. This can not extend anything.
  • An impl is a collection of methods either matching a Trait (like a Class implementing an Interface) or free form from a struct (like a Class), but Impl are not allowed to have variables and if implementing a Trait can not have methods that are not defined in the Trait. An Impl may be defined repeatedly.
  • trait and impl can used like extension Methods (although the syntax is closer to Swift than Kotlin).

There is nothing like an abstract class in Rust.

Some Kotlin examples:

interface ParentA {
	fun foo() //no method body, just an api
}

interface ParentB : ParentA { //includes methods from parent 
	fun bar() //no method body, just an api
}

class ClassFoo : ParentA { //includes methods from parent 
	var x = 0; //allowed to have variables with values 
	fun foo() {} //methods must be implemented
}

abstract class AbstractClassA: ParentA, ParentB {}

class ClassBar : AbstractClassA() {
	fun foo() {} //methods must be implemented 
	fun bar() {} //from all parents
	fun foobar() {} //can add new methods
}

fun ParentFoo.example1() {} //Adds method called example1 to all
//implementations and children of ParentA

Some Rust examples:

#![allow(unused)]
fn main() {
struct StructFoo { //variables only 
	x: i32
}

impl StructFoo {  //methods only, but can access 
	fn foo() {} //variables from Struct
}               //private methods allowed

trait TraitA { //API only 
	fn bar();
}

trait TraitC : TraitA {
	fn boo(); //anything implementing TraitC must implement TraitA separately
}

impl TraitA for StructFoo { //methods only, can not 
	fn bar() {}             //have methods not in the trait
}
}

Deriving and implementing

Let’s say you make a type: Person. It has a first name, last name, date of birth, and occupation. It has functions to get the whole name and their age.

use chrono::Date;
use chrono::offset::{*};

struct Person<'a> { 
	first_name: &'a str, 
	last_name: &'a str, 
	date_of_birth: Date<Utc>, 
	occupation: &'a str
}

//Constructors
impl <'a> Person<'a> {
	//no self param means this is a static method 
	fn new(first_name: &'a str,
		last_name: &'a str,
		year: u32,
		month: u32,
		day: u32,
		occupation: &'a str) -> Person<'a> {
		return Person {
			first_name,
			last_name,
			date_of_birth: Utc.ymd(year as i32, month, day), 
			occupation
		}; 
	}
}

//Methods
impl <'a> Person<'a> {
	//self param means this is an instance method 
	//as it's a reference this method does not consume 
	//the object
	fn whole_name(&self) -> String {
		return format!("{} {}", self.first_name, self.last_name); 
	}

	fn age_in_years(&self) -> i32 {
		let weeks = Utc::today().signed_duration_since(self.date_of_birth).num_weeks();
		return (weeks / 52) as i32;
	} 

	//the self here isn't a reference so the object
	//is consumed by this method and won't exist
	//after this is method is called
	fn into_tuple(self) -> (String, i32) {
		return (self.whole_name(), self.age_in_years());
	}
}

fn main() {
	//Double colon is for static methods
	let person = Person::new("John", "Smith", 1988, 07, 10, "Author");
	 
	//Period is for instance methods
	println!("{} is a {} who is {} years old.", 
		person.whole_name(), 
		person.occupation, 
		person.age_in_years());
}

The 'a lifetime tells Rust that the &strs will be available as long as the parent Person is. The sample uses the chrono crate, it is a simple to use and common date and time library. If we want to print the object we must implement the Display like this:

#![allow(unused)]
fn main() {
use std::fmt;

struct Person { 
first_name: String, 
last_name: String, 
occupation: String
}

impl fmt::Display for Person {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		return write!(f, "({} {}, {})", 
			self.first_name, 
			self.last_name, 
			self.occupation);
	} 
}
}

You can now write println!("{}", person), there are many traits that can be implemented for any struct that’s part of your project. To avoid boilerplate Rust can automatically derive some traits for structs like so:

#![allow(unused)]
fn main() {
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Default)]
struct Foo {} 
}
TraitUse
DebugAutomatically generates the equivalent of data classes toString(), use with {:?} instead of {}
CloneImplements the clone() method on the struct
CopyAllows structs to be cloned automatically instead of transferring ownership when assigned to new variable
PartialEqImplements equality checking and enables use of the == and != operators on the struct
PartialOrdImplements comparison and enables use of the > and < operators on the struct for types where the comparison may be impossible (e.g. floating numbers)
EqMarker trait (like Sync) meaning that all fields can be always and correctly compared, not valid for all types (e.g. floating numbers)
OrdSame as PartialOrd but for types where comparison is always possible
HashAutomatically generates the equivalent of data classes hashCode(), required to use the struct as key in HashMaps
DefaultImplements a default value for all fields, see Default

All of these require all the fields in the struct to implement the same traits. Numbers, strings, etc implement all the built in derivable types. As with PartialEq and Eq Rust often has two versions of a trait, one that is allowed to fail (and so will generally return Result or Option) and another that is not allowed to fail. In this case Eq will panic if something goes wrong, likewise there is From and TryFrom for converting structs, From will panic if it fails and TryFrom will return Err if it fails.

Default

If you implement a struct where all the fields have all implemented Default then you don’t have to write out every field when making a new instance of the struct:

#[derive(Default)]
struct Foo { 
	a: i32,
	b: i32,
	c: i32,
	d: String
}

fn main() {
	let foo = Foo::default();
	  
	//You can also supply some of the fields and leave the rest to Default:
	let foo2 = Foo { 
		b: 45,
		d: "Foobar".to_string(), 
		..Foo::default()
	};
	
	//This is also the syntax for copying:
	let foo3 = Foo { 
		a: 10,
		..foo 
	};
}

Methods

this/self

A method in a impl for a struct may have a param for the struct, it must always be the first parameter and does not have a name:

ParameterMeaningUsage
<None>A static method (accessed via ::)For constructors/builders or grouping methods
selfThe object itself (this means unless the method returns Self it will dropped after this method)Converting the object into another object
&selfA immutable reference to itselfGetting a value or perform a action that doesn't effect this object
&mut selfA mutable reference to itselfSetting a value or perform an action that changes internal values

An example of naming conventions:

struct Foo {
	...
}

impl Foo {
	//Constructor: new()
	fn new() -> Foo {}
}

impl Foo {
	//Getter: x()
	fn value(&self) -> i32]
	//Setter: set_x()
	fn set_value(&mut self, value: i32) {}
	//Is/Has: is_x()
	fn is_empty(&self) -> bool {}
	//Clone and convert: as_x()
	fn as_bar(&self) -> Bar {}
	//Convert: into_x()
	fn into_another_object(self) -> Bar {}
}

See also https://rust-lang.github.io/api-guidelines/naming.html

Functional Programming

Rust supports lambdas, the parameters are written comma separated in pipes and the body only requires curly braces if it goes over multiple lines:

#![allow(unused)]
fn main() {
	let x = vec![1,2,3];
	let y: Vec<usize> = x.iter()
		.map(|it| it + 1)
		.collect();

println!("{:?}", y);
}

Unfortunately with Rust (like Dart) map(), etc return a Map object that has to be converted back into a list using collect()

Rust also supports higher order functions:

fn foo(f: impl Fn(i32) -> i32)
fn foo<F>(f: F) where F: Fn(i32) -> i32 
fn bar(f: impl MutFn(String) -> usize)

Fn can not change external state

FnMut can change external state

FnOnce can change external state, but is only allowed to be called once

Box allows you to store values on the heap, this is sometimes necessary as the stack can only hold values with a known size (at compile time), as the Box is just a pointer it has a known size unlike, for example, lambdas.

Modules

When making a project in Rust you are required to have one file (for programs it’s main.rs, and lib.rs for libraries), it’s also the only file recognised by the compiler. To add a new file to your project you need to add the line (for a file named new_file.rs) to main.rs or lib.rs:

mod new_file;

Using the following code base:

//main.rs
mod foo; //all Rust files must be referenced here for the compiler to find them 
mod bar;

use crate::bar::foobar;

fn main() { 
	foobar();
}
//foo.rs
pub fn public_method() {} 
fn private_method() {}

//bar.rs
use crate::foo::public_method; 
pub fn foobar() {}

The foo module has two methods public_method and private_method. private_method is only accessible inside the foo module. The bar module imports the public_method method from the foo module.

crate means this project, if using a third party library (for example serde) you would write serde::foo::bar;.

Directories

When organising code it is common to group files in a directory. This requires a mod.rs file per directory, at minimum it must reference the other files in the directory to expose them to the compiler:

project 
├ main.rs
├ foo.rs
└ bar 
  ├ mod.rs
  └ inner.rs

//main.rs
mod foo; 
mod bar;

//bar/mod.rs
mod inner;

This would expose all files to the compiler.

Crates

Adding crates

Third party libraries are called Crates (and are available from https://crates.io). To add a crate, for example Serde, add this line to Cargo.toml after the [dependencies] line:

serde = "1.0.0"

You’ll still need to import the individual parts of the crate you want to use, for example:

use serde::json::Serialize;

::Foo means import just Foo

::{Foo, Bar} means import Foo and Bar

::* means import everything in the module.

You can rename when importing:

use example::Foo as Bar;

Most of the massive crates have a prelude module that you should import, i.e.

use chrono::prelude::*

Features

Some crates have optional features, often these include macros or provide interop with other crates:

serde = "1.0.0" can also be written as serde = { version = "1.0.0" }. To include a feature this gets expanded to serde = { version = "1.0", features = ["derive"] }.

Some crates will have features that you almost always want:

CrateFeature(s)Description
serdederiveAdds macros to automatically serialize structs
chronoserdeAllows DateTime, etc to be serialized by serde
reqwestjson, gzipAdds support for sending/received structs and adds automatic support for gzip

Not in standard

Some functionality built in to Java/Kotlin isn’t part of the Rust std lib and you’ll need to use these crates to add it:

FunctionalityCrateNotes
Random numbersrandMaintained by Rust team
SerializationserdeDoes XML, JSON, protobuf, etc
Lazy static variableslazy_static
RegexregexMaintained by Rust team
Base64base64
UUIDuuid
EnumsstrumEnum features variant names, properties, count, list, ordinal

Common crates

These crates are the closest equivalent to the commonly used Kotlin libraries:

KotlinRustNotes
GSON/MoshiserdeMuch more powerful and flexible than GSON
JodaTimechronoEssentially the same
JDBCdieselWorks with multiple databases

Result, Option and Exceptions

Result and exceptions

Result<V, E> is used for when a method may fail, it can contain the result or an error. It is created via Ok() and Err()

fn get(idx: u32) -> Result<bool, usize> {
	if idx > 10 {
		Ok(true)
	} else if idx > 20 {
		Ok(false)
	} else {
		Err(404)
	}
}

fn main() {
	let result = get(10);
	match result {
		Ok(item) => println!("{}", item),
		Err(e) => println!("{}", e)
	}
}

You can also do the equivalent of var!! with both Option and Result by using var.unwrap() and var.expect("some message"). Both methods will crash the app if it’s Err/None, expect() will also write the message to the console.

To avoid having to write unwrap() every time if you’re in a method that returns a Result you can just write var?, if var is an Err the method will return the Err immediately.

To crash a Rust program you should use panic!("message"). This will print the message and stacktrace to the command line.

Option and nulls

Option<T>s are the same as Optional<T>s and quite like nullable values and are created via Some() and None

fn divide(numerator: f64, denominator: f64) -> Option<f64> { 
	if denominator == 0.0 {
		None //notice no return and no semicolon 
	} else {
        Some(numerator / denominator)
    } //the last value in a method is automatically returned
} //assuming no return call 

fn main() {
	let result = divide(2.0, 3.0);
	match result {
		Some(x) => println!("Result: {}", x), 
		None => println!("Cannot divide by 0"),
	}
}

Creating macros

Macro template

macro_rules! <name> {
    (<args>) => {
        <body>
    };
}
  • <name> can be any valid rust ident
  • <args> see below
  • <body> see below

Arguments

Arguments can be empty, or combinations of variables and variadic.

Each arg must be the format $<name>:<type>. (i.e. $example:literal )

  • <name> can be any valid rust ident
  • <type> must be one of
typedescription
literalRust literal, such as a number
exprA single expression
tyRust type
identRust ident
and others

For example, this macro makes methods that adds two numbers:

#![allow(unused)]
fn main() {
macro_rules! add_numbers {
	($method_name:ident, $num_type:ty) => {
		fn $method_name(lhs: $num_type, rhs: $num_type) -> $num_type {
			lhs + rhs
		}
	}
}
}

When called with add_number!(add_i32, i32); this code is generated:

#![allow(unused)]
fn main() {
fn add_i32(lhs: i32, rhs: i32) -> i32 {
	lhs + rhs
}
}

Variadic

Arguments can be written as $( <arg> )* for 0 or more and $( <arg> )+ for 1 or more values. (i.e. $( $name:literal )+), this would allow example!(1 2 3).

To support commas you can write $( <arg> ),*, this would allow example!(1,2,3).

Optional

Anything can be optional using this syntax $( <thing> )?, this include commas, such as

  • optional trailing comma: macro_rules! example($var1:literal, $var2:literal $(,)?)
  • optional commas in variadic: macro_rules! list($( $items:literal )$(,)?*)

Body

The body must be an expression or single line, to support multiple lines surround the code in {}, this is often written as

macro_rules! <name> {
    (<args>) => {{
        <body>
    }};
}

Variadic

To use these arguments you have to surround them in $( <name> )* in the body. Anything can be written in the parentheses and it will be repeated once per item in <name>

For example if you want to print each item on a different line:

#![allow(unused)]
fn main() {
macro_rules! print_nums {
    ($( $numbers:literal ),+) => {{
        $( println!("{}", $numbers); )*
    }};
}
}

If called with print_nums(1,2,3); This generates

#![allow(unused)]
fn main() {
println!("{}", 1);
println!("{}", 2);
println!("{}", 3);
}

Optional

Optional arguments should also be surrounded like $( <name> )*, anything can be written inside like before.

When dealing with optional args you may need to get an alternative value

#![allow(unused)]
fn main() {
macro_rules! add_nums {
    ($num1: expr $(, $num2: expr)?) => {
        $num1 + $( $num2 )*
    };
}
}

This works fine if called with add_nums!(1,2) but with add_nums!(1) the code generated would be 1 + which fails to compile.

To get around this use something to offer a substitute such as

#![allow(unused)]
fn main() {
macro_rules! some_or_none {
    () => { None };
    ($entity:expr) => { Some($entity) }
}

//or

macro_rules! or_else {
    ($value:literal, $other: literal) => { $value};
    (, $other: literal) => { $other };
}
}

These are used like this

#![allow(unused)]
fn main() {
macro_rules! add_nums {
    ($( $num1: expr )? $(, $num2: expr)?) => {
        or_else!($( $num1 )*, 0) + or_else!($( $num2 )*, 0)
    };
}
}

Overloading

Macros can support different argument sets:

#![allow(unused)]
fn main() {
macro_rules! add_nums {
	($num1:literal, $num2:literal) => { $num1 + $num2 };
	($num1:literal, $num2:literal, $num3:literal) => { $num1 + $num2 + $num3 };
}
}

This can be called with add_nums!(1,2); and add_nums!(1,2,3);

Hygiene

Macros have 'hygiene' which means to access something from your crate you'll have to spell out the full path:

$crate::path::some_method(..)

$crate refers to your crate.

Visibility

Macros have to be annotated with #[macro_export] for it to be usable by other modules/crates.

Advanced

Macros will accept extra text which can be required to invoke the macro:

#![allow(unused)]
fn main() {
macro_rules! add_nums {
    ($num1: literal + $num2: expr) => {
        $num1 + $num2
    };
    (($num1: expr) + $num2: expr) => {
        $num1 + $num2
    };
}
}

Would have be called like this add_nums!(1 + 2) or add_nums!((some_var) + 2)

With custom text in the arguments then either commas are needed to separate them macro_rules! example($thing1:expr, $thing2:expr) or all but the last expr must be declared and called surrounded by parentheses macro_rules! example(($thing1:expr) $thing2:expr) and example!((thing1) thing2);

See more Macros by example

Concurrency

To pass values safely between threads you need to use Mutexes or Atomic values in most languages, Rust is no different. For example:

use std::thread;
use std::sync::atomic::{AtomicI8, Ordering};
use std::sync::Arc;
use std::time::Duration;

fn main() {
    //AtomicXX implement Sync meaning they can be used 
    //in multiple threads safely
    //Arc (Atomically Reference Counted) allows for multiple, independent
    //references of a single value to exist outside of the borrow checker
    //for a tiny overhead by counting the number of references that
    //exist
    let number = Arc::new(AtomicI8::new(0i8));
    //Make a copy of the arc, any number of copies can exist but
    //the each copy has to be moved into it's thread
    let thread_number = number.clone();

    //move means this lambda is taking ownership of any variable
    //it uses, this is necessary for lambdas executed in a different
    //context e.g. in a different thread
    thread::spawn(move || {
        let mut i = 0;
        loop {
            //ordering controls how the atomic value is set/read
            //You should probably always use SeqCst
            thread_number.store(i, Ordering::SeqCst);
            i += 1;
            thread::sleep(Duration::from_millis(500));
            if i > 10 {
                break;
            }
        }
        println!("Done");
    });

    loop {
        println!("{}", number.load(Ordering::SeqCst));
        if number.load(Ordering::SeqCst) >= 10 {
            break;
        }
    }
}

This program will continually print out the value stored in number until the thread reaches 10

Testing

The standard in Rust is to have the tests in a module inside the module being tested, the test module needs to be annotated as do all the tests:

#![allow(unused)]
fn main() {
//foo.rs 

fn add(value1: i32, value2: i32) -> i32 {
  value1 + value2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_all() {
        assert_eq!(2, add(1, 1));
    }
}
}

Cargo

To run and build programs from the command line you should always use cargo (outside of an IDE):

#Build debug version
cargo build

#Run debug version
cargo run

#Run tests
cargo test

#Build release version
cargo build --release

Other command line options:

#Format all code
cargo fmt

#Linter
cargo clippy

#These have to be installed first by
rustup update
rustup component add rustfmt
rustup component add clippy

Tools

To install a tool use cargo install <tool>, for example cargo install cargo-edit

Dependency management

Description

Allows you to add, remove or update dependencies from the command line

Usage

cargo add <crate>, e.g. cargo add chrono

cargo rm <crate>

cargo upgrade

Link

cargo-edit

Dependency graph

Description

Generates a dependency graph for your project

Usage

cargo deps

Link

cargo-deps

Security Audit

Description

Audit Cargo.lock files for crates with security vulnerabilities reported to the RustSec Advisory Database.

Usage

cargo audit

Install commands

cargo install cargo-audit

Link

cargo-audit

Macro Expansion

Description

Prints out the result of macro expansion and #[derive] expansion applied to the current crate.

Usage

cargo expand [module]

Install commands

cargo install cargo-expand

Link

cargo-expand

Outdated dependencies

Description

Prints out report of out of date dependencies.

Usage

cargo outdated

Install commands

cargo install cargo-outdated

Link

cargo-outdated

Enums

Unfortunately enums in Rust work they do in Swift and so no default values can be provided and instead you have to add methods which use matches to provide values:

enum MobileOs {
  Android, Ios, Windows
}

impl MobileOs {
  fn status(&self) -> &str {
    match self {
      MobileOs::Android => return "alive",
      MobileOs::Ios => return "alive",
      MobileOs::Windows => return "dead",
    }
  }
}

fn main() {
  println!("{}", MobileOs::Android.status());
}

Thankfully they can also work like sealed classes:

enum Example {
  Foo { named_value: i32 },
  Tuple(u8, u8),
  Empty
}

fn main() {
  let foo = Example::Foo { named_value: 45 };
  let tuple = Example::Tuple(1, 2);
  let empty = Example::Empty;
}

When coming from other modern languages you be expecting the ability to get a variant count, list or names and add static values to each variant but unfortunately Rust enums do not support any of these features, but all of these can be added with the strum crate.

Strum

The strum crate adds many features to enums on a case by case basis.

It provides:

  • To and from string for enum variants
  • Variant iterator
  • Enum count
  • Property values
  • etc

Each feature needs to be enabled by adding a derive macro.

Tips and tricks

Minor tips

Reading a line from the command prompt using std::io::stdin().read_line() will include the return character, remove it using trim()

if let

Like in Swift an Option can be unwrapped with an if, if there is a value match:

#![allow(unused)]
fn main() {
fn some_method(optional_string: Option<String>) {
  if let Some(string_value) = optional_string {
    println!("Does exist: {}", string_value);
  }
}
}

This also works for Result but use Ok and Err instead of Some and None. If you need to handle both states you should use match as an if let throws away the other value:

#![allow(unused)]
fn main() {
type Error = Box<dyn std::error::Error>;

fn some_method(optional_string: Option<String>) {
  match optional_string {
    Some(string_value) => println!("Does exist: {}", string_value),
    None => println!("No content")
  }
}

fn another_method(result: Result<String, Error>) {
  match result {
    Ok(string_value) => println!("Success: {}", string_value),
    Err(err) => println!("Failure: {}", err)
  }
}
}

Reference counting

Sometimes you need bypass the borrow checker, for example, you want to use a reference as a pointer to an item in a collection or you’re passing values between threads. To do this you use the Arc (Atomically Reference Counted) class, it adds a small overhead in the form of a count and some extra code to monitor and update the count. Arc will keep the value alive as long as any Arc value is still alive, when the last Arc value is dropped the value will be as well. To make multiple references to an value protected by Arc clone it:

use std::sync::Arc;

fn main() {
  let some_heap_thing = Thing::new();
  let arc_thing = Arc::new(some_heap_thing);
  thread1(arc_thing.clone()); 
  thread2(arc_thing.clone()); 
}

fn thread1(thing: Arc<Thing>) {
  thing.methods_accessed_in_normal_way();
}

fn thread2(thing: Arc<Thing>) {}
struct Thing {}
impl Thing { 
fn new() -> Thing {Thing {} } 
fn methods_accessed_in_normal_way(&self) {}
}

The value in the Arc can not be mutable unless it’s contained in another class, as the value will need to be protected against concurrent updates, the wrapper types are Mutex and RwLock. The differences are that RwLock will allow any number of concurrent readers but only one writer and Mutex only allows one reader or writer at a time.

Converting strings

Often when writing a function that takes a piece of text you’ll want to support both String and &str to be more convenient to the caller. This is best achieved by using &str or Into<String>.

String can be converted to &str like this:

let text = String::new();
print(&text);

(Actually it's converted to &String as adding a & just makes it a reference but &String implements AsRef<str> allowing it to be automatically converted)

The Into trait tells the compiler to allow any parameter that be coerced as that type to be passed in. &str already has the Into<String> trait but it can also be implemented for any struct. Into<X> for Y is automatically implemented for any type that implements From<Y> for X which is actually how it’s implemented for Strings and is the recommended approach.

fn print(value: &str) { 
	println!("{}", value;
}

fn print<S: Into<String>>(value: S) { 
	println!("{}", value.into());
}

Interior mutability

Sometimes you need to have a mutable value but can only pass it around as a value or reference, to achieve you can use the Cell structs.

Cell is a wrapper around a value that can be changed at any point.

RefCell is the same as Cell but allows the value to be exposed as a reference.

RwLock is the same as RefCell but can be shared across threads.

Mutex is the same as RefCell but can make references that can be shared across threads.

All of these are safe, they use reference counting and/or memory swapping to update values.

Example:

use std::cell::RefCell;

fn main() {
    let not_mutable = Person {
        name: RefCell::new(String::from("Emma Britton"))
    };

    not_mutable.change_name(String::from("New Name"));

    println!("{}", not_mutable.name.borrow());
}

struct Person {
    name: RefCell<String>
}

impl Person {
    fn change_name(&self, new_name: String) {
        self.name.replace(new_name);
    }
}

Indexed iteration

Kotlin provides forEach, map, filter, etc for iterators but these only give you the element if you also need the index as Kotlin provides forEachIndexed, mapIndexed, filterIndexed, etc

list.forEach { element ->
	println(element)
}
list.forEachIndexed { i, element -> 
	println("${i}: ${element}")
} 

Rust also has this feature but it works differently, instead of different methods the iterator type is changed via enumerate:

#![allow(unused)]
fn main() {
let list = vec!["a", "b", "c"];
list.iter()
	.for_each(|element| println!("{}", element));

list.iter()
	.enumerate()
	.for_each(|(i, element)| println!("{}: {}", i, element));
}

This has the downside that all later operators must also handle the index as well.

Formatting strings

To format a string, the easiest (and correct) way is to use format!(string, parameters...). String formatting in Rust uses {} as placeholders for parameters.

Formatting is supported by several methods:

  • format!(fmt, values...) - returns a formatted string
  • print!(fmt, values...) and println!(fmt, values...) - writes a formatted string to stdout
  • write!(Formatter, fmt, values...) - writes the formatted string into the first parameter

You can pass values into these macros without making them a reference first as they will be turned into references, and so the macros don't take ownership.

Basics

With

format!("{}", some_value)

If some_value implements Display then it will printed otherwise you'll get a compile error saying the type doesn't implement Display. All primitives implement Display, but other std types like arrays and collections do not.

How to implement Display for a custom type:

struct Point {
    x: i32,
    y: i32
}

impl Display for Point {
    fn fmt(&self, f: &mut Formatter) -> Result<(),fmt::Error> {
        write!(f, "Point({},{})", self.x, self.y)
    }
}

then when calling format!("{}", Point {x: 2, y: 3}) results in Point(2,3).

Another option exists though, for example if you don't really care about the formatting: Debug, which is implemented like this

#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32
}
}

To print the debug version of a value use {:?} (or {:#?} to pretty print), calling this format!("{:?}", Point { x: 3, y: 5}) results in Point { x: 3, y: 5 }

All primitives and std types like arrays and collections, and most structs from third party crates, implement Debug.

Param selection

There are several ways to order the params

Default

They are used in the order supplied

#![allow(unused)]
fn main() {
println!("{}, {}, {}", 1, 2, 3);
}

produces 1, 2, 3

Positional/Indexed
#![allow(unused)]
fn main() {
println!("{1}, {2}, {0}", 1, 2, 3);
}

produces 2, 3, 1

Named

Non named parameters must come before any named parameters

println!("{0}, {foo}, {bar}", 3, bar = 1, foo = 2);

produces 3, 2, 1

Referencing variables
#![allow(unused)]
fn main() {
let foo = 1;
println!("{foo} = {}", 2);	
}

produces 1 = 2

Named params can formatted with debug by adding :?, format!("{foo:?}")

Padding

"{:>5}" Left pad with up to 5 spaces

"{:<7}" Right pad with up to 7 spaces

"{:^22}" Centre with up to 11 spaces on both sides

Padding with any character:

"{:_>5}" Left pad with up to 5 underscores

"{:#<7}" Right pad with up to 7 hashes

"{:c^22}" Centre with up to 11 ’c’s on both sides

Numbers

"{:.3}" Will print 3 fractional digits (adding 0s on the end if necessary) but only if it’s a floating point number

"{:+3}" Print sign

"{:03}" Print at least 3 digits (padding with 0s on the start if necessary), if negative the minus symbol will replace a 0

Example: format!("{:>5} {named}", "Foo", named=123)

To have variable parts (such as variable length padding) use this syntax:

("{1:.0$}", 1, 1.22)

This will print 1.2, the syntax is {value_index:.precision_index$}

("{1:=<0$}", 10, "test")

This will print test======, the syntax is {value_index:padding_char<length_index$}

Note that invalid parameter details are ignored silently and treated as {}. If debugging via logging consider using dgb!():

let x;
x = dbg!(1 * 4); 

prints [src/main.rs:3] 1 * 4 = 4

For crates

Date Formatting

Chrono

Using DateTimeFormat.forPattern("<pattern>").print(DateTime.now()) for Kotlin and Utc::now().format("<pattern>") for Rust.

Kotlin/JodaTimeRust/ChronoExample
Dateyyyy-MM-dd%Y-%M-%D2000-01-01
Text Datedd MMM yyyy%e %b %Y15 Jun 2004
TimeHH:mm:ss%H:%M:%S14:12:56
to ISO 8601DateTime.now().toString()Utc::now().to_rfc3339_opts( SecondsFormat::Millis, true)1996-12-19T16:39:57.000Z
from ISO 8601DateTime.parseUtc::parse_from_rfc_3339

JSON Parsing

Serde

Add this to cargo.toml:

serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Kotlin

data class Example(
    val name: String,
    @SerializedName("num")
    val someNumber: Int
)

fun convert(json: String, example: Example) {
    val outputExample: Example = Gson().fromJson<Example>(json, Example::class.java)
    val outputJson: String = Gson().toJson(example)
}

Rust

#[derive(Serialize,Deserialize)]
struct Example {
    name: String,
    #[serde(rename = "num")]
    some_number: i32
}

fn convert(json: String, example: Example) {
    let output_example: Example = serde_json::from_str(&json).unwrap();
    let output_json: String = serde_json::to_string(&example).unwrap();
}

Dependency Injection

Silhouette

Rust

use silhouette::facade::Container;

// will always use the same pool
Container::singleton(&|_| DBPool::new())?;

// will resolve a new connection each time
Container::bind(&|container| -> DBConnection {
    let shared_pool = container.resolve::<DBPool>().unwrap();

    shared_pool.get_conn()
})?;

// somewhere else in your app...
let connection: DBConnection = Container::resolve()?;

Common bugs/issues

Cloning a reference returns a reference despite the signature being a value

This can because the struct doesn’t implement/derive Clone

cannot move out of borrowed content when using unwrap()

This is because unwrap() consumes the reference (it’s self parameter is just self), to fix this use variable.as_ref().unwrap().

No method named .. found for Rc<RefCell>

Most likely you have imported use std::borrow::BorrowMut, remove it. The error is caused because there are two methods named borrow_mut and importing the trait causes the wrong one to be called. See GitHub issue

closures can only be coerced to fn types if they do not capture any variables

You need to change the param to a generic, for example:

fn main() {
    let value = "example";
    
    print(|text| format!("{text}{value}"));
}

fn print(method: fn(&str) -> String) {
    println!("{}", method("hi"));
}

into

fn main() {
    let value = "example";
    
    print(|text| format!("{text}{value}"));
}

fn print<F: Fn(&str) -> String>(method: F) {
    println!("{}", method("hi"));
}

Architecture

Kotlin is an object orientated language which means that generally data and methods are grouped together in classes and it’s very rare to have methods outside of classes even though Kotlin fully supports this. Also classes can extend each other so a lot of frameworks (such as Android or AWT) rely heavily on inheritance to provide functionality to classes.

Rust is a data orientated language, and programs will more resemble C programs where a program may be made of a few methods and a few structs without any impls. And as impl inheritance is impossible in Rust and trait inheritance doesn’t really add much use, composition is used a lot.

That said grouping logic and data is still fine and done with Rust:

class Point( 
	val x: Float, 
	val y: Float
){
	fun distanceTo(other: Point): Float {...}
	fun angleTo(other: Point): Float {...} 
}

can become

struct Point {
	x: f32,
	y: f32 
}

impl Point {
	fn new(x: f32, y: f32) -> Point {
		return Point {x, y}; 
	}
}

impl Point {
	fn distance_to(other: Point) -> f32 {...}
	fn angle_to(other: Point) -> f32 {...} 
}

Resources

Tutorials

Linked List An in depth guide on how to implement a linked list in Rust

Tic-Tac-Toe This project is heavily commented and is an example of idiomatic and well written Rust.

Rust Community

Official subreddit

Subreddit for beginners

Discord

Weekly newsletter

References

Language reference

Guide for changes in 2018 edition of Rust (This should only be needed for converting old code)

Other

Playground

Rust: A Unique Perspective