Getty Images/iStockphoto
Rust rises in popularity for cloud-native apps and environments
Rust is making a name for itself in cloud. See why developers are putting in the effort to adopt the programming language for cloud-native development.
Over the last several years, a relatively new programming language called Rust has surged in popularity among both cloud vendors and developers.
Originally sponsored by Mozilla, Rust is a close-to-the-metal systems language that prioritizes performance and memory safety over all else. This focus has prompted tech vendors from Amazon and Microsoft to Dropbox and Discord to experiment with Rust in their production pipelines.
Before choosing Rust, examine its benefits alongside potential issues in cloud-native development. Also, examine Rust examples on ownership, moving versus borrowing, and mutability.
Benefits of Rust in cloud
Because Rust doesn't need a garbage collector, it can offer convenient access to low-level details without the confusing overhead of manual memory management. The language provides direct access to hardware and memory safely. This reduces developer issues and delivers some built-in security protections. It has allowed companies like Dropbox to reduce memory consumption on critical systems that require that same memory for other purposes.
But what does this have to do with the cloud?
Without a runtime or garbage collector, Rust relies on an extremely aggressive compiler that catches and eliminates any unsafe or erroneous code before it is exposed to an end user. With a front-loaded effort, the resulting binary is exceptionally fast. The benefits of safety, security and speed have become a favorite among high-load cloud-native applications. Such applications must squeeze out every ounce of performance to keep operating costs low.
Drawbacks of Rust in cloud
While the language delivers some obvious benefits, especially in environments that require the highest level of performance, the learning curve is said to be exceptionally steep.
Taylor ThomasSenior software engineer at Microsoft
Migrating from a language like Go to Rust can be a huge hit on developer productivity, as the language paradigms are quite different. While developers can cut some corners when it comes to memory management in Go, they must be intentional about memory usage in Rust -- which can slow down overall development speed.
That said, many organizations have accepted this tradeoff, as it often means that the end product is more maintainable and requires less debugging.
"The flexibility, safety and security of Rust outweighs any inconvenience of having to follow strict lifetime, borrowing and other compiler rules -- or even the lack of a garbage collector. These features are a much-needed addition to cloud software projects and will help avoid many bugs commonly found in them," said Taylor Thomas, senior software engineer at Microsoft.
Rust examples
It is difficult to distill the benefits and drawbacks of Rust into a single, concise application or function example. After all, the improvements seen by companies like Discord were achieved after porting already highly optimized services over to Rust from another language like Go. That said, there are a few key features built into Rust that should provide an excellent overview of the types of problems it is well-designed to solve.
Ownership
One of the most unique concepts in Rust is ownership. Unlike PHP, where the function that creates the variables inherently owns them, Rust ownership is a much more fluid concept that has direct parallels to how ownership works in the real world.
Take, for example, the following PHP function that prints out a variable:
function main() { $variable = "hello, world"; hello($variable); } function hello($variable) { echo $variable; }
The value of the variable $variable is passed into the function hello(), where it is then printed out on the screen. At all times, the main() function owns the $variable variable, even if it is passed by reference into hello().
Now, let's look at the same concept in Rust:
fn main() { let variable = String::from("hello, world"); hello(variable); } fn hello(variable: String) { println!("{}", variable); }
Looks similar, right? Not quite. While the variable is still passed into hello(), its ownership actually changes. That means if you tried to access variable within the main() function after reassigning its ownership to the hello() function, you would receive a compiler error because the variable is no longer available. It has been moved.
Moving versus borrowing
When you own something, you can either let someone borrow it or you can let them have it. When you let the person have it, you permanently pass on your ownership -- in Rust, this is called moving. In the previous example, the variable was moved into the hello() function. It doesn't exist in the main() function anymore, which is how Rust offers such memory safety. Developers must be very explicit with their memory management.
But what if you want to access a variable after passing it into a function? This is called borrowing. Similar in syntax to passing by reference -- but not in practice -- you can achieve borrowing in Rust by prefixing the variables in question with an ampersand: &. For example, if we wanted to let our hello() function simply borrow the variable instead of owning it, we would write the code like this:
fn main() { let variable = String::from("hello, world"); hello(&variable); } fn hello(variable: &String) { println!("{}", variable); }
By including "&" in our function signature and parameter, we are telling Rust that the underlying function only wishes to borrow the variable in question, not own it. That way the main() function can still access it after hello() has a chance to use it for a little while.
(Im)mutability
One caveat to borrowing is that, by default, variables are immutable. In our example above, we can see that the hello() function can borrow the variable, but if it attempted to actually change that variable, the compiler would throw an error. While the concept of mutable borrows is one that Rust supports, there are some significant limitations for the sake of safety, so we'll instead touch upon simple mutable variables.
Variable definitions in Rust are accomplished using the let directive. To make a variable mutable, it must be indicated at definition time using the mut directive. For example:
fn main() { let mut variable = String::from("hello, world"); hello(&variable); variable = String::from("goodbye, world"); hello(&variable); } fn hello(variable: &String) { println!("{}", variable); }
After we let hello() borrow the variable, we then change the value to "goodbye, world" and let hello() borrow the new value.
Key takeaway: Embrace the tradeoffs
While the shift in thinking required to develop in Rust can turn off some developers, a common thread among companies that have embraced the language is that slow is steady, and steady is fast.
There's more to productivity than development speed, such as rework. As developers, we have accepted that bugs happen, but what a language like Rust excels at is the drastic reduction of bugs you will encounter in the wild. While the learning curve or code style may slow you down, you gain so much more in the long-term maintainability of your product.