Advice to write a paper on software engineering and Rust

Hi Rustaceans!

I’m currently taking a master level software engineering course and have to write a paper on a topic of my choice.
Since I grew interested in Rust and more or less followed its development for two years now, I’ve been thinking about making my paper on how softwares are written in Rust compared to C/C++. But even though I followed it’s development for some time now, I don’t consider myself competent in Rust because I didn’t work that much with it, and that’s why I’m making this post to gather suggestions. I want to make it more than a simple homework.

  • What kind of pitfalls can you fall in when designing a software using C or C++ and how Rust could allow you to get a better architecture?
  • How can Rust allow you to build a safer software while maintaining a clean API?
  • I’m considering using a specific type of software as a case study (after all, Rust may not be the best answer for everything, right?). At the moment I’m thinking about using game engines. But it’s probably too big and I should probably focus on a particular component of game engines. Would you happen to know what part would be a good candidate?
  • Do you know specific patterns that are error prone in C/C++ but easily avoided / made safe in Rust?

I’m also interested in anything besides these that you consider especially important in Rust.

Best regards,
CBenoit

I think that a good resource could be Using Rust For Game Development by Catherine West from the RustConf 2018. The talk is focused on game developing, but Catherine expresses some concepts that are applicable in other fields. She shows some real code in C++ they developed for their game, Starbound, and how they ended up with a very complex designed. Then she tries to do the same thing with Rust, showing that even the most simple case cannot be compiled using the same approaches, forcing the developers to find new ways of organizing their code in order to obtain a coherent and sound result.

2 Likes

What kind of pitfalls can you fall in when designing a software using C or C++

Safety

There are no guarantees about the code.
A wide range of unsafe practices are permitted.
Writing safe code requires more effort than writing unsafe code.

API

This depends on whether you’re comparing C or C++.

C

In C, you can write code that exposes a public API which can be consumed by any programming language. However, the language is unaware of modern concepts that would make it easier to write efficient software with flexible APIs. The lack of generics and algebraic data types, for example.

C++

In C++, you do have access to templates, but the language is still deeply rooted in C idioms. To take advantage of newer language concepts, you need to be aware of, and expend more effort to write your APIs around them. It’s not going to be an elegant solution. It also lacks a package manager.

Rust could allow you to get a better architecture

Safety

The language provides static guarantees about the code.
Unsafe practices are prohibited outside of an unsafe scope.
Writing unsafe code requires more effort than writing safe code.

API

Since Rust is a newer language that hasn’t ignored past research, it does ship with modern language concepts as part of the core language. They’re very easy to use, and deeply encouraged / required. It doesn’t have the legacy cruft that C++ suffers from. Cargo makes distributing and retrieving dependencies simple, as well.

One of the neat things is that we can guarantee that you’ll be able to compile a project from 10 / 20 years ago with the exact dependencies as the project originally required, due to the immutability of Crates.io. Rust is not yet old enough to test that claim, but it’s theoretically possible due to how the Cargo.lock file works. I think this is critical for certain applications which require it (ie: scientific resarch).

How can Rust allow you to build a safer software while maintaining a clean API?

This is somewhat answered already. The borrow checker can ensure that the API is handling memory safely, as well as ensuring that the caller is using the API correctly. The type system can additionally be used to ensure that only certain methods are available for specific inputs, such as using a newtype to wrap a type and exposing methods that can only be used with that newtype.

As for a clean API, this is merely made possible through updated language concepts, such as algebraic data types and generics. Try comparing union types in C to enums in Rust, then imagine building an API around them and forcing callers to use that.

Rust may not be the best answer for anything

I think Rust can be great for everything, eventually. The language is less important than the ecosystem. Yet Rust has a great language that could enable a stronger ecosystem that’s easier to work with. Though I think Julia has a place among scientific research and teaching basic programming, whereas Rust would be great for writing libraries for Julia to interact with.

Would you happen to know what part would be a good candidate?

At System76, we use it for all of our system-critical infrastructure. From kernels to system services. We’ve found it to be good for writing desktop applications for Linux with GTK, too. If you want a project to have a high chance of success, you’ll likely want to use Rust. Requires that you own some experienced Rust programmers, though, of course.

Do you know specific patterns that are error prone in C/C++ but easily avoided / made safe in Rust.

I think this is best answered by reading up on the research papers that Rust was based on. The Cyclone language research project, for example. As mentioned on the website, perhaps the most critical pattern that Rust solves is the ability to write highly multi-threaded software that shares and manipulates data across threads, safely. Solving that problem solves many other problems. Yet it also solves a lot of other issues, such as constructing types that we can store references which are guaranteed to remain valid.

One thing I think is interesting is that Rust makes working an entity-component systems easier, due to generics. This can solve a lot of the same problems that object-oriented programming solves, but with more efficiency and less room for error. Due to the static guarantees, generational arenas and ECS is often a good solution for a wide variety of problems where you may want to store a lot of entities that may reference each other, or to have the ability to add and remove components from an entity at runtime.

I’m also interested in anything besides these that you consider especially important in Rust.

Rust, as a language, is really powerful. Yet that is only part of the equation. Perhaps even more important is Cargo.

Learning to write software in Rust is simple because building a Rust project just requires typing cargo build. Dependencies are fetched automatically via Cargo before compiling, and will always be available, which will make Rust a huge success for students in schools. Newcomers will find it easy to locate libraries on Crates.io, search documentation on them from Docs.rs, and to publish crates of their own with Cargo.

The crate ecosystem also enables for rapid development and distribution of libraries for Rust. It’s easy to publish a crate, and to continue to iterate upon it. NPM allowed for JS development to explode in popularity. Cargo can do the same for Rust. This is something that neither C or C++ have a parallel to.

5 Likes

What kind of pitfalls can you fall in when designing a software using C or C++ and how Rust could allow you to get a better architecture?

With Rust I can define APIs that are harder to misuse. Concepts that in C need to be documented, remembered and manually followed, can be automatically enforced in Rust.

  • In C when I call foo(&obj) I don’t know if foo will store the pointer and use it later, or whether it’ll only inspect or copy the data before returning. In Rust lifetime declarations make that explicit contract of the function, and the compiler ensures it’s followed.

  • In C I may accidentally call disconnect(database); query(database) and there’s nothing in the language that can define this is an invalid order of operations. Thanks to ownership and move semantics, Rust can prevent this (e.g. fn disconnect(owned: DbHandle) takes the handle and the compiler will ensure there’s no code that could use it after that).

  • In C thread-safety of functions is not formally specified. I rarely see documentation discuss this problem beyond saying something is or isn’t “thread-safe” in general sense. Rust’s Send and Sync concepts define — and enforce — fundamentals of thread-safety very well.

With these three features I can create a zero-cost Rust API wrapper around C code, and make the C code easier and safer to use. I can translate informal conventions and caveats from C libraries’ documentation into specific Rust interfaces — this is like RTFM done by Rust at compile time.

4 Likes

Rust’s error handling especially shines.

In C error handling is ad-hoc and depends on the function. Some functions return 0 on success (and an error code otherwise), some return 0 (meaning false) on failure. Some set errno. It all has to be documented, and the programmer has to remember it and check it every time.

OTOH Rust has Result and Option types, which are widely used and well understood. The compiler will enforce that they are always checked.

When C code is written with robust error handling in mind, a huge amount of code is spent on explicit error handling and proper resource deallocation on every error. You’ll see deeply nested ifs, status variables, or goto cleanup all over the place. It’s noisy, tedious, and hard to get completely right all of the time. Given effort required, it’s of course tempting to cut corners in error handling.

OTOH in Rust the ? (try) operator, as well as RAII and automatic memory management make error handling relatively easy. Resource cleanup on error is almost always handled automatically. Most of the time handling is as terse as a single ? symbol.

3 Likes

Thanks to all of you for your great answers! I took note of everything.

I’ll dive into Catherine West’s conference that seems to be closely related to what I’m looking for.

I’m extremely grateful to you mmstick for this elaborated answer full of interesting points.
Getting this kind of feedback from someone who uses Rust at work is gold.

Thank you for the pointer on Cyclone.
I assume you are referring to papers found the Rust Forge’s Blibliography: https://forge.rust-lang.org/bibliography.html
I’ll also try to skim through others papers and see what I can extract.

Getting this kind of feedback from someone who uses Rust at work is gold.

We’ve found that error rates in Rust projects are significantly more rare than in C, Vala, & Python projects. Errors in Rust are always related to simple logic bugs that are easy to track down without a debugger. Python especially is pretty dangerous because a simple API change can have devastating results for the end user if they happen upon a broken code path (treating a string as an array, for example). Therefore, with Rust, we can spend more time implementing features, and less time debugging issues.

We will ultimately attempt to write everything in Rust, as a result. After all, when you have a company that depends on the software you write being both reliable and maintainable, and can’t afford mistakes, you’ll want to use the tools that make that possible. Rust can guarantee that for both the code, and the skill of the programmer that’s capable enough of writing a solution to the problem in Rust. If your solution works in Rust, and you didn’t use any unsafe code at all, you probably did things right. Less need to waste time reviewing code. If QA tests the new code and it does what it says without breaking anything, it’s good to go.

As a side effect, Rust experience also makes us better C programmers. So if we need to hack in C for some third party open source projects, we’re all capable of tackling problems effectively with best practices.

3 Likes