Go and Rust, not Go versus Rust

In the last few months I have been playing around with Rust, Mozilla’s new systems programming language. I worked on small personal projects and submitted a few pull requests (mostly refactoring) to Servo, a Mozilla research project to build a browser engine in Rust.

Another language that often gets mentioned alongside Rust is Go, also known as golang since Go is hard to search for (so is Rust to a lesser extent).

Both languages are compiled, static, strongly typed and have pointers. Both are at a lower level than popular languages like Ruby or Python. They both have been called systems programming languages though this description fits Rust more than it does Go.

I have often seen people asking which one is better or which language should be chosen over the other.

Because of this appearance of opposition and the fact that I already knew Rust, I decided to try Go and see if and how the languages were similar.

Initial impressions of Go

My initial impressions of Go are very good. The language is easy to learn and is nicely designed. If you know any of the following languages; C, C++, Java or C# you’ll be able to jump right in after reading a short tutorial.

The tooling is great, particularly gofmt which formats your code automatically according to the Go style guidelines. I’m using a Vim plugin for Go which calls gofmt each time I save. This process is very nice once you start using it.

One downside is how the package/module/directory structure needs to be set up. It’s not clear when your first start how everything must be structured. It’s important to read this part of the documentation before starting your first project. Otherwise you’ll end up like I did, needing to rearrange the file structure of my git repository because I didn’t follow the proper structure.

Even now that I know the correct file structure I still find it a bit odd and constraining. It’s definitely not one of the language’s strong point.

Comparing Go and Rust

While both languages may seem to serve a similar purpose at first, upon using both they are very different. I have seen many people asking which one is better but the answer depends on what you are building and what you are looking for in a language.

Go is easier to learn. There is deliberately less features in the language and you can hit the ground running after a short tutorial.

Programming is also easier. I spent more time solving problems with my algorithms than I did wrestling with the language.

Go is also garbage collected. That means less to worry about as you don’t have to manage memory. On the other hand, for some applications a garbage collector can be a big downside and it does mean less control.

Rust on the other hand is a more complex language. There is a lot of interesting things in the language and if you’re a language nerd you will love a lot of it’s features. This makes learning and mastering the language harder than Go, but it also makes for a powerful language with a lot of expressive concepts like meta programming through syntax extensions.

I have spent a lot of time battling with the Rust compiler. Compared to Go this means I spend more time solving problems related to the language itself rather than to the algorithms I am writing.

One thing I must say is that when a program finally does compile, there are much less run time errors than in any compiled language I have tried before.

The language isn’t garbage collected and also allows for more control like choosing if memory is allocated on the stack or the heap for example.

Conclusion

Go is a general purpose utilitarian language that’s suitable, amongst other things, for building tools and app back-ends.

Rust is a “PHD smarts” language that’s better suited for systems level stuff and programs where memory management and performance is important.

Go will probably become a go to language when I need a compiled portable low-level language. It’s more modern than C and it’s simpler than C#.

Rust on the other hand is more specialized and is a better choice when doing systems level programs, when garbage collection isn’t an option, or when performance is critical.

Both are wonderful languages and I intend to use them both going forward.

Rust: Traits

Rust 0.12

Traits in Rust are similar to interfaces in a language like C#. The definition of a trait is that they are a collection of concrete methods that are used to extend an existing class using a name resolution policy to prevent multiple inheritance conflicts.

In Rust, traits are divided into a trait block which defines methods and an implementation block for each type which implements the trait. You can see the combination of these two components as the trait. This division allows us to implement methods at two level; at the general trait level and at the type level. When defining a default definition at the trait level it is not necessary to explicitly implement said method at the type level unless we wish to override the default with a type specific implementation.

Unimplemented method definitions must be implemented by a type which implements the trait or this will raise a compile time error.

struct Counter {
    hits: u32,
}

trait Logger {
    fn log(&self, message: String);

    fn write_state_to_log(&self);

    // Provide default method implementation
    fn line_break(&self) {
        println!("");
    }
}

impl Logger for Counter {
    fn log(&self, message: String) {
        println!("{}", message);
    }

    // if we were to omit one of these methods we would get 
    // a compile time error
    fn write_state_to_log(&self) {
        println!("{}", self.hits);
    }

    // no implementation for line_break
}

In this previous example we first define a struct called Counter and a trait called Logger. The third method contains an implementation while the first two are only signatures.

We can then use it as such:

fn main() {
    let counter = Counter { hits: 0 };
    counter.line_break();
    counter.log("Test message".to_string());
}

We can also make a method receive a parameter of type trait rather than a concrete type. This allows us to work with abstractions in our functions.

fn receive_trait<L: Logger>(logger: L) {
    logger.write_state_to_log();
}

fn main() {
    let counter = Counter { hits: 0 };
    receive_trait(counter);
}

In the previous example we create a function with a generic type L which must implement the Logger trait.

Implementing multiple traits

A type can implement multiple traits. Let’s add another one:

trait OutputsToConsole {
    fn output(&self, message: String);
}

We can implement it and call it like so:

impl OutputsToConsole for Counter {
    fn output(&self, message: String) {
        println!("{}", message);
    }
}

fn main() {
    let counter = Counter { hits: 0 };
    counter.line_break();
    counter.log("Test message".to_string());
    counter.output("Test output".to_string());
}

But what happens if two traits have a method with the same signature and we implement both traits for the same type? How will Rust know what method to call?

Let’s find out:

// code for Logger omitted

trait OutputsToConsole {
    fn output(&self, message: String);

    // new method with the same signature as
    // a method in Logger
    fn line_break(&self);
}

impl OutputsToConsole for Counter {
    fn output(&self, message: String) {
        println!("{}", message);
    }

    fn line_break(&self) {
        println!("");
    }
}

fn main() {
    let counter = Counter { hits: 0 };
    counter.line_break();
}

Compiling this piece of code produces the following output:

main.rs:58:5: 58:25 error: multiple applicable methods in scope [E0034]
main.rs:58 counter.line_break();
^~~~~~~~~~~~~~~~~~~~
main.rs:15:5: 15:25 note: candidate #1 is `Logger::line_break`
main.rs:15 fn line_break(&self) {
^~~~~~~~~~~~~~~~~~~~
main.rs:43:5: 45:6 note: candidate #2 is `Counter.OutputsToConsole::line_break`
main.rs:43 fn line_break(&self) {
main.rs:44 println!(“”);
main.rs:45 }
error: aborting due to previous error

Non-surprisingly, the Rust compiler gives out an error. The way to call the proper method isn’t so clear though. There is a proposed syntax that would allow to define the correct method to call but it hasn’t been implemented in the language as of this writing (remember Rust is pre-1.0).

In the meantime, one way to do it would be to have a function which receives a generic type which is constrained to the trait we want and call the method from this constrained generic parameter.

Returning a trait

Having a function return a trait is not something that is as easy as I would have expected. You can’t simply define a function as such:

fn return_trait(&counter: Counter) -> Logger {
    // doesn't work
}

To do this we need some other concepts which I will cover in another post.
For now we still have a basic idea of how to use traits.

Using a local crate with Cargo

Using a local crate with Cargo

The Cargo web site provides a good explanation of how to use Cargo with external libraries found on GitHub but it does not give any examples of how to use libraries or crates which are local projects.

Today I came upon a question on StackOverflow asking how to do this. I had also created an issue on GitHub relating to this, so I decided to write this post.

Basic project structure

These instructions assume that you are using the default Cargo project structure which is initialized when you call cargo new project-name.

If you added your Cargo.toml file after creating your project you can conform to the Cargo structure by moving your sources in a src/ folder, creating a lib.rs in project-name/src/ and putting your Cargo.toml file under project-name/.

For the project’s structure here’s what you’ll end up with where project is the name of your project and local_dependency the name of your component/sub-project.

project/
├── Cargo.toml
├── src
│   ├── local_dependency
│   │   ├── Cargo.toml
│   │   ├── lib.rs
│   │   └── source_file.rs
│   ├── lib.rs
│   └── main.rs

Your Cargo.toml in your main project’s root should look like this:

[package]
name = "project"
version = "0.0.1"
authors = ["That guy over there!"]

[dependencies.local_dependency]
path = "src/local_dependency"

Since your local dependency is a separate project it will need it’s own Cargo.toml file which will should like this:

[package]
name = "local_dependency"
version = "0.0.1"
authors = ["Somebody else"]

[lib]
name = "local_dependency"
path = "lib.rs"

The lib.rs in src/local_dependency/ will contain the public declarations of your modules.

pub use source_file::SomethingUseful;

pub mod source_file;
 

For reference here is source_file.rs:

pub enum SomethingUseful {
    Really,
    Nah
}

The lib.rs file in src/ is the library file for your main program which you leave empty until you need it.

Finally, in your main source file (src/main.rs) you can reference parts of your local dependency likewise:

extern crate local_dependency;

use local_dependency::SomethingUseful;

fn main() {
    println!("Hello world!")
}

Here you go.

Good enough!

In the last few weeks I have started contributing to Servo, a Mozilla Research project. The project is hosted on GitHub but instead of doing code reviews using the GitHub interface it uses a third party website called OperaCritic.

When my pull requests are accepted it displays the following (click for a bigger version):


justGoodEnough

The first time I read it quickly and I had to re read it again to make sure if it was a good message or not. Found it pretty funny 🙂

I like it when people (or the things they create) don’t take always take themselves too seriously.