Getting Started with Enigma: The Rust Programming Language
By: Aditya Palepu
Tags: Development, Privacy, Ethereum, Smart Contracts, Rust

In preparation for Discovery, we are going to be releasing a three-part “getting started” series for developers. This series will cover:

a guide to Rust an introduction to Secret Contracts and setting up the Enigma developer environment building a front end for our Enigma-powered dApp

Part 1: A guide to Rust

In order to prepare for writing secret contracts, we will first write a standalone program that will introduce you to the Rust programming language. Converting this program to a secret contract afterwards is actually a pretty quick and frictionless process, so following along with our work here will be invaluable in the near future.

What follows is an end-to-end walkthrough guide where we will build a standalone Rust program — no decentralization, no privacy, no blockchain buzzwords — that naively implements Yao’s Millionaires’ Problem. For those of you unfamiliar with this particular problem, we encourage you to check out an earlier post that followed our initial protocol release for further info. Essentially, it is a problem that was introduced in the early 80’s by Andrew Yao, and studies the scenario where two millionaires (Alice and Bob) want to know which one of them is richer without ever revealing his or her net worth to one another.

While it is this privacy caveat that makes this problem truly interesting, we will focus on implementing a naive solution to this first, which will serve as the foundation for writing secret contracts later on. Our goals here are to:

explore the motivations behind using Rust as our language of choice for writing secret contracts assist in setting up your Rust environment help develop the Millionaires’ Problem code, while touching upon some important Rust concepts

Why Rust? Because It Is Awesome

Rust is an excellent programming language that simultaneously optimizes for both safety and performance in a way few languages can claim. The Rust core team has created a language that allows for grizzled veteran C++ developers and absolute newcomers alike to develop production-ready, systems-level code as swiftly and confidently as ever.

It accomplishes this with a unique and powerful combination of the cargo package manager (overlooked in higher level languages), zero-cost abstractions, memory management that is both manual and smart in nature (thereby avoiding both the runtime garbage collection costs you find in Python and Ruby as well as the error-prone nature of manual management in C and C++), and many other features! The strict compilation checks and error explanations Rust offers prevents large classes of common errors, likely serving as your best friend in writing safe and performant code. Last, but certainly not least, there is a wonderful community of Rustaceans (a weird, but endearing term that will grow on you over time, I promise) that will support whatever your journey may be. Joining this community puts you in good company — as one recent survey shows, Rust has been voted the most loved programming language for the third year in a row!

Everything from here on out is a hands-on guide for setting up your machine and writing a Rust program. Before we dive in, I want to add a quick personal note. Everyone has their own learning styles. Some prefer jumping right in, while others enjoy spending more time on the fundamentals from the beginning. Neither approach is better than the other, as it is a matter of personal preference. However, I feel the official Rust book is uniquely excellent at explaining the various concepts (with amazing examples) in a concise, and often light-hearted, manner. Many of the topics covered below definitely need further reading and reinforcement, and additional resources are linked where appropriate — so if anything does not make sense or you just want to learn more, I encourage you to do so and check out the official book resources!

Setting Up Rust

The recommended way of setting up Rust is to follow the instructions beautifully laid out in the official Rust guides, which can be found here. It is just a couple quick steps and should only take a few minutes!

Millionaires’ Problem

Now that you have Rust set up and good to go, let us create our first project.

As a first step, create a brand new project using the cargo package manager with the following commands:



cd rust_millionaires_problem # navigate to directory c argo new rust_millionaires_problem # create a new projectcd rust_millionaires_problem # navigate to directory

Within your project’s root, there should be a file called Cargo.toml . This file, otherwise known as the project’s manifest, includes the name, version, and author identifiers (along with other metadata). This file should auto-populate accordingly if you have set up Rust in its default configuration, but just in case, it should look something like this:

[package]

name = "rust_millionaires_problem"

version = "0.1.0"

authors = ["<name> <<email>>"]



[dependencies]

The cargo new command we ran above magically creates a working “Hello, world!” program for us, which we can test out using cargo run (compiles and runs the code) like so:

$ cargo run

Compiling rust_millionaires_problem v0.1.0 (<path>/rust_millionaires_problem)

Finished dev [unoptimized + debuginfo] target(s) in 1.62s

Running `target/debug/rust_millionaires_problem` Hello, world!

Yay! “Hello, world!” is pretty sweet, but we will soon replace this with the more exciting millionaires’ problem logic. Rust programs consist of either binary or library crates. By default, the cargo new command creates a binary executable, rooted at src/main.rs , which contains the “Hello, world!” logic at the moment. While you could in theory implement the entirety of what we will cover here in this single file…library-driven development is highly recommended, especially as your projects grow, so we will create a new file src/lib.rs . This will serve as the library crate root containing the business logic for our program., which we will call from main.rs .

Program Design

To begin implementing, let us first refresh ourselves with what we are building again:

Create a new Millionaires’ Problem (this object should maintain a list of millionaires)

Add first millionaire (an object containing the address and net worth) with name Bob and net worth 1,000,000

Add second millionaire (an object containing the address and net worth) with name Alice and net worth 2,000,000

Compute the richest millionaire, returning the appropriate address/name

Awesome — now that we have mapped things out, we will tackle each item one-by-one!

Data Structs

The most basic data type we will need to code is one that represents an individual millionaire. We will choose to represent a millionaire using a Rust struct — “a custom data type that lets you name and package together multiple related values that make up a meaningful group.” Structs are defined with an appropriately-chosen name and fields (field name coupled with associated type). The first bit of code we will write in our src/lib.rs file will look like this:

pub struct Millionaire {

address: String, // <field name>: <field type>

net_worth: u64, // <field name>: <field type>

}

In other words, every Millionaire will contain two tidbits (fields) of data: its address (String type) and its net_worth (numerical, u64 type).

We will get to this in the next section, but it is worth noting how structs are instantiated in Rust. If you were, for example, to instantiate a Millionaire struct that adheres to the definition above, you could do that by stating the name of the struct, and listing key: value pairs within curly braces like this:

let alice = Millionaire {

address: String::from('Alice'),

net_worth: 2000000,

}

The let keyword seen here is used to indicate that we are setting a variable’s value, in this case alice .

A Millionaires’ Problem will also be represented by using a struct, so we can go ahead and add on to the src/lib.rs file in this manner:

pub struct MillionairesProblem {

millionaires: Vec<Millionaire>,

}

What we have done here is implement a struct named MillionairesProblem with a single field, millionaires . This field will contain a list of Millionaire structs (defined above), which can be represented as a vector (a single data structure containing multiple values of the same type). Thus, the data type for this field has been chosen to be Vec<Millionaire> .

Struct Function and Method Implementations

For those of you more familiar with object-oriented programming, we can define methods on our MillionairesProblem struct. To achieve this in Rust, we add an impl (implementation) block:

impl MillionairesProblem {

//TODO

}

Take special note that this impl block has the same name as the MillionairesProblem struct we defined above, thus functions and methods we implement here refer to the same context.

Creating a new Millionaires’ Problem

If you recall from the flow chart depicted above, the first function we should implement is one that creates a new millionaires’ problem. Within the impl block, let us go ahead and add the following:

pub fn new() -> MillionairesProblem {

MillionairesProblem {

millionaires: Vec::new(),

}

}

Let us break this down:

In Rust, functions are defined using the function keyword, fn , followed by an appropriately-chosen name (in this case, new ), a set of parenthesis with any necessary parameters (in this case, none), and a return type if necessary (in this case, -> MillionairesProblem ).

, followed by an appropriately-chosen name (in this case, ), a set of parenthesis with any necessary parameters (in this case, none), and a return type if necessary (in this case, ). The body of a function is contained within the curly braces and can contain expressions (evaluate to a resulting value) or statements (action-performing instructions with no return value). Expressions do not end with a semicolon, whereas statements do. Although we could return a value explicitly using the return keyword with a value, we can alternatively (as is most commonly done in Rust) implicitly return the final expression in the block. Thus, the function definition written above simply contains an expression (no semicolon) creating a new MillionairesProblem struct object.

Although we could return a value explicitly using the keyword with a value, we can alternatively (as is most commonly done in Rust) implicitly return the final expression in the block. Thus, the function definition written above simply contains an expression (no semicolon) creating a new struct object. Speaking of this MillionairesProblem struct object, note how it has been instantiated: the struct name, followed by the curly brace-enclosed key: value pairs, which in this case just reads millionaires: Vec::new() . In other words, we have designed things so that a new struct is initialized such that the millionaires field contains an empty vector. This empty vector is created using Vec::new() , where the :: indicates that we are using an associated function of the Vec type. Associated functions are those that are implemented on a particular type rather than a specific instance of that type (a la static methods in other languages). Vec::new() is a function immediately available to you since the Vec module is automatically exposed as part of the std library that is by default imported in every Rust program. In fact, this new() function is actually an associated function in and of itself since it does not deal with a particular instance of the MillionairesProblem struct, but rather is associated to this struct and will create a new instance that can be handled later. As you can imagine, we will eventually call this function using the syntax MillionairesProblem::new() , as per the usual associated function syntax.

Adding a Millionaire

In order to add a new millionaire, we will define a method in the impl block like this:

pub fn add_millionaire(&mut self, address: String, net_worth: u64) {

let millionaire = Millionaire {

address,

net_worth,

};

self.millionaires.push(millionaire);

}

The method signature is defined much the same as the new() function we wrote above with a couple key differences we will briefly discuss:

We have no return type, thus no -> followed by return types.

followed by return types. We expect parameters to the function. In Rust, we signify this using the format param_1: <type>, param_2: <type>, …, param_n: <type> .

. Methods, unlike traditional functions and associated functions (like the new() function above) always start with self as the first parameter, which represents the particular instance of the struct this method is being called on. In this case, we prefix self with &mut . The & sign touches upon the topic of ownership (Rust’s most unique and powerful feature). I highly recommend you read through this concept to better understand what is going on, but essentially we do not want this function to take ownership of the instance this method is operating on. We simply want to reference its value. mut is another important topic — by default all Rust values are immutable. If we were only reading from the MillionairesProblem struct, we do not have to worry about changing its state and could use &self as the first parameter. However, since adding a millionaire involves changing the millionaires vector field of this instance, we include the mut prefix to indicate that we will be modifying the instance in this regard.

Much like we did above, we instantiate a new Millionaire struct. You may notice that we do not have the traditional key: value syntax you may have expected, but, rather minimally, only the key names. This is a little bit of syntactical sugar that is allowed since the key and the parameter variable names (the values) are the same.

Lastly, using the self instance we have at our disposal, we append this new millionaire struct to our millionaires vector field.

Computing the Richest Millionaire

The final method we will write is to compute the richest millionaire:

pub fn compute_richest(&self) -> &str {

match self.millionaires.iter().max_by_key(|m| m.net_worth) {

Some(millionaire) => &millionaire.address[..],

None => "None"

}

}

Once again, we have a very similar method signature to the ones we have defined above. This time however, we use &self without the mut modifier since we’d like to simply read the list of Millionaire structs this instance of the MillionairesProblem struct contains, without modifying it. We also return a type of &str , which a String slice type, corresponding to the Millionaire struct’s address/name field.

A new concept we have not encountered before is the match keyword, which allows us to match a particular value against a “series of patterns and then execute code based on which pattern matches.” In this case, we want to find the wealthiest Millionaire struct instance based on net_worth . We accomplish this with this line: self.millionaires.iter().max_by_key(|m| m.net_worth) . This returns an Option type, from which we can match against the Some or the None patterns. If there is a valid value inside, the control flow will execute the Some pattern, successfully extracting the Millionaire struct instance as the variable millionaire , and implicitly returning a string slice reference corresponding to the address attribute. Otherwise, the control flow will execute the None pattern’s code and return the string literal, "None" .

Summary of Library

By now, we have completed our first Rust library together! To summarize, we first created two structs: MillionairesProblem and Millionaire . We then created an impl block to further the functionality of the MillionairesProblem struct we have defined with an associated function to create a new instance, and two methods to allow us to add a new millionaire and compute the richest millionaire. Our final src/lib.rs file contents should look like this:

pub struct Millionaire {

address: String,

net_worth: u64,

}



pub struct MillionairesProblem {

millionaires: Vec<Millionaire>,

}



impl MillionairesProblem {

pub fn new() -> MillionairesProblem {

MillionairesProblem {

millionaires: Vec::new(),

}

}



pub fn add_millionaire(&mut self, address: String, net_worth: u64) {

let millionaire = Millionaire {

address,

net_worth,

};

self.millionaires.push(millionaire);

}



pub fn compute_richest(&self) -> &str {

match self.millionaires.iter().max_by_key(|m| m.net_worth) {

Some(millionaire) => &millionaire.address[..],

None => "None"

}

}

}

Interacting with the Library

We will now just spend a few minutes writing the last bit of code to interface with the business logic we have set forth in the library file above. Edit your src/main.rs file to look like this:

extern crate rust_millionaires_problem;

use rust_millionaires_problem::MillionairesProblem;



fn main() {

let mut millionaires_problem = MillionairesProblem::new();

millionaires_problem.add_millionaire(String::from("Bob"), 1000000);

millionaires_problem.add_millionaire(String::from("Alice"), 2000000);

let richest = millionaires_problem.compute_richest();

println!("Richest millionaire = {}", richest);

}

The first two lines bring the library file’s contents, and specifically the MillionairesProblem struct, into scope.

struct, into scope. The main function, as you saw in our initial “Hello, world!” program, is a special function in Rust — it is the first entry point in every executable program.

function, as you saw in our initial “Hello, world!” program, is a special function in Rust — it is the first entry point in every executable program. Within the body of main , we create a new instance of a MillionairesProblem using the associated function new() we defined in our library.

, we create a new instance of a using the associated function we defined in our library. We go ahead and add two new millionaires, Bob and Alice. Each one is added using the add_millionaire method defined on the MillionairesProblem struct, taking a String value for the address/name and a number for the net worth.

method defined on the struct, taking a String value for the address/name and a number for the net worth. Finally, we call the compute_richest method on our MillionairesProblem struct to return the address/name corresponding to the wealthiest millionaire and save this value off to the variable, richest .

method on our struct to return the address/name corresponding to the wealthiest millionaire and save this value off to the variable, . We print this variable out in our console using the println! macro. The curly braces act as a placeholder for variable data that we pass in as additional arguments, in this case, the richest variable.

Now, if you execute cargo run from the root directory in the terminal, you should see something that looks like this:

$ cargo run Compiling rust_millionaires_problem v0.1.0 (<path>/rust_millionaires_problem)

Finished dev [unoptimized + debuginfo] target(s) in 0.69s

Running `target/debug/rust_millionaires_problem`

Richest millionaire = Alice

Voilà! As you would expect based on the data we input above, Alice is indeed the richest millionaire!

Conclusion

Congratulations! In this walk-through guide, we successfully set up a local Rust environment and created our first library-driven Rust program to execute (naively) the Millionaires’ Problem. We covered important topics such as libraries, structs, vectors, implementation blocks, associated functions, methods, references, mutability, and match statements. Rust is an extremely powerful language, and while we hope this guide has given you a taste of what you can do, we highly urge you all to peruse through The Rust Programming Language official book for a more in-depth understanding of the above topics (and more!).

In the next post in this series, we will explore how to transition this program into an Enigma-powered secret contract. This is where we start looking at the really exciting stuff — encrypted state, secret computations, etc. — empowering you to start working on the building blocks that will power the way applications can scalably address data privacy.