Refactoring development

Ramblings from the trenches...

View on GitHub

Rust

It seems a long time ago now, but I recall the reasons why I first really took to Java. It was a safe place to be, with no operator overloading, implicit casts and checked exceptions. It is a far cry from the shotgun that is C++ (very easy to blow your own legs off). - For example in Java (unlike C#) you always knew that something on the left of the ‘.’ was null when you got a null pointer exception. (Rust goes one step further by not having a ‘null’.)

It was an easy language to reason with - few surprises. In a business context where someone’s taken the liberty to pre-write a million lines of code for you, few surprises counts for a lot.

Or at least Java was an easy language to reason with until the classloaders started loading classloaders…

Safety seems baked into the Rust language, from the lack of C-style for (int i=0; i<…;i++) loops, to every variable being owned by something. Checked exceptions make another appearance (though in a more pattern matching guise). Variables are immutable by default but when mutable only one thread can alter them at any one time. The list goes on and on of little tweaks here and there that will add up to more robust (and faster) code being written.

Rust was meant to be just a systems programming language but I suspect they’re going to wildly overshoot the mark.

The cost of maintaining a business’s software keeps becoming a greater percentage of the business’s overall costs - the software is the business for many. As such, maintainable code is cheaper. Cost wise a Rust codebase seems a cheaper codebase to maintain.

Pre-rust, I used to just call a function with some arguments. Post-rust I’m now forced to consider whether I want to send over a mutable or immutable version of each argument. There is a mental leap that needs to happen to code in Rust, but this is a one-off cost amortisable away.

 

Compile time Test Coverage

This one is really neat, - something they really should call out as a ‘feature’ of Rust. When you run ‘cargo test’ to run your tests, it first compiles the code. Nothing special here you might think, but main() is presumed to not exist and your tests are presumed to be called. This means if you’ve not tested a chunk of code then it gets picked up by the #[warn(dead_code)] checker.

How beautiful is that? compile time test coverage. Sweet.

(Oh, and assertions failing somehow show a great failure message rather than just assertion failed at this line number.)

Rust FTW

Right now, Rust is probably the safest most efficient language to code in. It’s a fairly high level language with few compromises. If you have 100-10000+ developers in your company policing threadsafe code in c++/java/c# is pretty much impossible - you have to fall back on many processes. With Rust in an enterprise environment threadsafe code is a much more achievable goal.

I love the phrase ‘fearless concurrency’ - in a multi-core world, this is the language we need. History I suspect will show Rust as being the first major breakthrough in language design of the 21st century.

There’s always a catch… in Rust’s case it is a steep learning curve. There’s learning the syntax and the libraries. This is much like the learning cost of Java, Python or C#. But then if Rust was just like Java or Python then the world would not have moved forward.

The second half of learning Rust is understanding the language’s constraints and re-learning all the patterns of programming. I suspect Rust is easier to teach to a fresh programmer than someone who has coded for years as it does require you to think differently and for me at least that takes time.

Getting Started

So now that I’ve badgered you into submission, here’s some great ways to get stuck in:

Lessons learned

I am by no means great at coding rust, I’m still on that learning curve, but hopefully some of the things I’ve noticed may ease your learning of rust. So while these tips maybe helpful, there’s probably nicer, even more rustonic ways of doing things.

Options

Option<T> is used a lot in rust as an alternative to a nullable type. To get at the value you can match against it:

match my_option {
Some(val) =&gt; {println!("{:?}", val); },
None =&gt; {}
}

This is great, but a little verbose. It turns out you can do pattern matching ifs like this:

if let Some(val) = my_option {
println!("{:?}", val);
}

Which is a lot neater if you only care about one branch.

Making friends with the compiler

If you’ve come from a manage language the compiler/borrowchecker can be a little angry at first. The simplest rule to not annoy the compiler is this: pass all function arguments by reference, return by value.

I.e.

fn my_compiler_friendly_function(a: &amp;A, b: &amp;B) -&gt; C {

}

my_compiler_friendly_function(a, b);

In general the above is what you want to do - you’re passing non-mutable references into the function which means that you can use a and b after the function call. You want to not return a reference because if you do you would need to let the compiler know how long it’s going to live for - i.e. you’d need to introduce an explicit lifetime parameter.

The above is just a way to simplify getting started with rust - there’s a lot to learn and by keeping to the above you can learn a lot of rust without having to also concentrate as much on lifetimes.

The same applies to print also:

let world = String::new("world")
println("Hello {}", world)
println("Hello {}", world)

You can’t do this as by passing without a reference to println it’s gobbling up ownership of the world String. You want to do println(“Hello {}”, &world) - almost always for prints pass by reference.

Rust on OSX

OSX is the poor man’s relation to Rust on Windows and Linux. They get all sorts of newfangled things like debuggers and line numbers in the stack trace. Alas on OSX lldb’s frame variable command returns nothing at the moment and line numbers on OSX stacktraces are for wimps.

So to celebrate Rust turning two today I’ve written my first macro using an excellent tutorial on macro_rules!

macro_rules! log(
($first:expr) =&gt; { {
println!(concat!("{}:{} ", $first), file!(), line!());
} };
($first:expr, $($arg:expr),*) =&gt; { {
println!(concat!("{}:{} ", $first), file!(), line!(), $($arg),*);
} }
);

Now the above may not look pretty but in place of println!() it will tell you where each log message was output. And hyperlinked to the file location in intellirust!

Could use location of matching rust macro as a structural search and replace in intellirust