Open jaheba opened 7 years ago
Great explanation. :+1:
In this post, I want to discuss the newtype-pattern, as well as the From and Input traits , which help with conversion between types.
Did you mean From
and Into
traits?
Hello, @jaheba I inform you, that I translated this article in Russian and posted it on habrahabr.ru Thank you for it!
Thanks for this great writeup! The From implies Into link needs updating.
Rust is an elegant language, that is quite different from many other popular languages. For example, instead of using classes and inheritance, Rust has a trait-based system. However I believe, that many programmers new to Rust (including myself) are unfamiliar with common Rust patterns.
In this post, I want to discuss the newtype-pattern, as well as the
From
andInput
traits , which help with conversion between types.Let's say we work for a european company building fancy, digital, IoT-ready thermostats for heaters. To ensure that water in heaters doesn't freeze (and thus damages heaters) we ensure in our software, that if there is a danger of freezing, we let hot water through. Thus somewhere in our software we have this function:
It takes some temperature (provided by some WiFi-connected sensors) and adjusts the flow of water accordingly.
Everything goes well, customers are happy and no damaged heaters are found. Management decides to expand to the US and our company finds a local partner, which bundles their sensors with our state-of-the art thermostat.
It's a disaster.
After some investigation it is revealed that the American sensors reported temperatures in Fahrenheit, whilst the software for our thermostats works with Celsius. The software starts heating as soon as the temperature falls below 3° Celsius. Unfortunately, 3° Fahrenheit is way below the freezing point. Luckily, after a software update we can fix the problem and the damage is limited to just a few 10-thousands US-Dollars. Others weren't so lucky.
Newtypes
The problem occurred, because we associated floating-point numbers with something more than just numbers. We have given these numbers a meaning without communicating it explicitly. Thus, instead of using plain numbers to represent temperature, we basically want to bundle them with a unit. Types to the rescue!
This is what Rustaceans call the
newtype
-pattern. It is a struct boxing a single value in a tuple-struct. In the example we created two newtypes, one each for Celsius and Fahrenheit.Using these, our function in question now has this type-signature:
Using this function with anything but Celsius-values results in compile time errors. Success!
Conversions
All we have to do now is to write conversion functions, which can turn one unit into the other.
And then use them like this:
From And Into
Conversion between different types is quite common in rust. For example we can turn
&str
toString
usingto_string
, similarly to above:However, it is also possible to use
String::from
to create a string like this:And even this:
So why all these functions, when they are seemingly doing the same?
Into the Wild
Rust offers traits, which unify conversions from one type into another.
std::convert
describes among others theFrom
andInto
traits.As we can see above,
String
implementsFrom<&str>
and similarly&str
implementsInto<String>
. Actually, one has to only implement one of those two traits to gain both, since they are basically the same thing. To be more precise, From implies Into.So let's do the same for temperatures:
Applied to our function-call:
Your Wish Is My Command
Now, one could say that not much is gained by using the
From
trait over just implementing conversion functions -- as we did before. One could even argue the opposite,into
is much less descriptive thanto_celsius
.What we can do though, is to move the unit-conversion into the function:
This function now magically accepts both Celsius and Fahrenheit as inputs, whilst remaining type-safe:
We can even go a step further. Not only can we process a multitude of convertible inputs, but also produce several output-types in the same way.
Let's say we want a function, that returns the freezing point. It should return either Celsius or Fahrenheit -- depending on the context.
Calling this function is a bit different from other functions where we easily know the return type. Here we have to request the type we want.
There is a second, more explicit way to call the function:
Boxed Values
This technique is not only useful to convert units into each other, but can simplify handling of boxed values, e.g. query results from databases.
Summary
Python has a beautiful Zen. It first two lines say:
Programming is the act of communicating intention to the computer. And we should be explicit with what we actually mean, when we write programs. For example, it is un-descriptive to use a boolean value to encode sort-order. In Rust we can just use an enum, to eliminate any ambiguity:
In the same way newtypes help to attach meaning to plain values. A
Celsius(f64)
is different fromMiles(f64)
although they may share the same internal representation (f64
). On the other hand the use offrom
andinto
help us, to keep programs and interfaces simpler.