Comparing Rust and C#

This blog post contains perspectives on similarities and differences between the rust programming language and the C# programming language for .NET. This post focusses on command line and background processes and does not consider native user interface technologies for Windows or Linux, although for now at least, C# is almost certainly more appropriate for Windows developers and rust may be more appropriate for Linux UI developers. I am not a professional programmer; this post provides my opinions based on about 20 years of experience with C# and a few weeks with rust.

This blog is mostly about C# and .NET. I recently started a separate blog about my learnings with rust.

Why Rust and C#?

I know of numerous alternatives to rust and C#. I try to focus on the languages that I consider to have the greatest advantages over others. From my perspective, C# has always had significant advantages over C, C++, Java, and anything interpreted, from Unix shell scripting to JavaScript, which have their place in gluing things together, but I generally consider to be inappropriate for enterprise applications. I use Unix shell scripts and there are cases where I must use JavaScript, but other than for specific requirements, without strong arguments in their favor, I would not consider any languages other than C# and rust for new development.

Cross-platform

Both rust and C# are available for Windows and Linux including Windows Subsystem for Linux. I have not checked, but one or both are probably available for Apple and android devices. I do not have time to consider client-slide programming, even in the browser, although I am considering WebAssembly and Blazor (C# to WebAssembly), and I understand that rust may support WebAssembly.

Development Tooling

The primary development environment for .NET is Microsoft Visual Studio for Windows, although JetBrains provides an alternative in Rider, which runs on at least Windows and Linux. My primary development environment for rust, and I think for many rust developers, is Microsoft Visual Studio Code, which runs on Windows or Linux and can have advantages over Visual Studio itself in terms of least performance, simplicity, command shell integration, additional modern user interface conventions that Visual Studio itself has not applied, and otherwise, but lacks some capabilities for rust. I have not evaluated Visual Studio Code plugins for rust, but I like coding rust in Visual Studio Code (I prefer the Windows version on Windows) even without any rust plugins. I would evaluate these development environments as approximately equivalent for the underlying languages.

For some specific tooling differences, see the sections of this post about debugging and disassembly.

Devops Tooling

While .NET has made significant advances on the command line, I prefer the tooling for developer operations in rust. Commands like rustfmt, cargo-check, cargo-clippy, and cargo itself (like the dotnet command, but somehow simpler and seeming more capable). It is quite easy to automate relatively complex build and deployment processes.

You could say that the devops tooling for .NET includes all sorts of capabilities that rust does not have, such as containerization and deployment to AWS and Azure. Some of these capabilities are less relevant to rust, but others indicate that the focus of rust is not on the types of applications that deploy to these environments, but on their underling platforms such as Linux.

The rust compiler is incredibly smart but does not always provide the best suggestions for correcting issues. The .NET compiler is probably just as smart and often provides better guidance. Both compilers require some time for developers to get familiar with their handling of common syntax errors. Due in part to its scoping rules, I believe the rust compiler may be more likely to detect logic errors.

Syntax

The syntax of both languages is quite similar, although each has constructs that the other does not. Some of these differences are relatively trivial, such as rust treating a bare expression without a semicolon at the end of a block as a return statement. Other differences are significant and affect value, such as rust’s use of enumerations that can contain values, where enumerations are simple types in .NET. Conversely, C# supports Language Integrated Query (LINQ), and while rust has some comparable constructs, they are relatively complicated and not necessarily implemented consistently in every library.

Performance

In most cases, I think that assembly language or even laying out an executable by hand may not have significant performance advantages over rust, where rust has significant performance advantages over .NET.

Resource Requirements

While .NET is efficient, for almost any use case, I do not believe that .NET can ever achieve the efficiency of an executable compiled from rust. Again, flexibility and developer productivity weighs into any choice of technologies.

Standard Libraries

There is no question that the libraries provided by .NET are far more comprehensive than those provided by rust. This is a significant difference. Rather than having default implementations for typical logic such as JSON parsing, rust requires use of external libraries, some of which may be maintained by the same engineers that maintain rust, but many of which are not. This cannot possibly improve the consistency of the codebase for libraries used by a majority of developers, and leads to multiple implementations of common functionality such as HTTP clients. While there are advantages to competing implementations, .NET only provides default implementations such as System.Text.Json; developers can choose to use alternates such as Newtonsoft.Json.

External Libraries

While there are external libraries available for both technologies, they are far less relevant for .NET, which includes significantly more functionality in its standard libraries. Additionally, Microsoft maintains many of the external libraries used by most .NET developers, where third parties maintain a greater percentage of important external libraries for rust.

Update 26.June.2021: It appears that a package that one of my projects referenced updated a global configuration file for cargo to use /tmp as the build directory. Hence, the cargo clean command attempted to remove all files and subdirectories from /tmp. I consider the need to use external packages that have such abilities to be a major security vulnerability for the entire rust toolchain. For a deeper understanding of this issue, read the following rust forum thread and the resources to which it links.

Library Management

.NET is pretty good for library management, but adding references to libraries can be a bit of a hassle (maybe I should be editing .proj files instead of using the Visual Studio UI). Adding libraries to a rust project is easier, but there are some disadvantages. I have had my share of package version conflicts with .NET, which .NET Core reduced. I fear something similar for rust, especially as Visual Studio often assists with such conflicts, and I am not sure that Visual Studio Code can address what appears to be a potentially higher level of complexity in rust dependency/version graphs.

Dependencies are another issue in rust. Packages for .NET tend to have minimal dependencies. Packages for rust can have significant dependencies, which increases build times and introduces large quantities of unknown code into the solution. Rust itself may be safe, but this requires trusting code contributed by numerous parties, some of which may have pernicious intent, and especially if such a resource is somehow compromised.

Memory Management

C and C++ require developers to manage memory, which increases complexity and, at least in those languages, leads to code safety issues. C# is safer, tracking variable scope and provides garbage collection, which consumes additional memory and can affect performance. While tight memory control has advantages in terms of resource consumption, at least relative to developers managing memory, garbage collection has advantages in terms of developer productivity. And from one perspective, unused memory is wasted memory anyway, although that perspective could lead to systems that require more memory over time.

Anyway, rust releases memory automatically when their ownership goes out of scope. This requires rust developers to consider memory management, scope, ownership, and lifetime even though they do not allocate memory manually. Eventually, this must become second nature, but it presents difficulties for developers transitioning from more languages that are more common.

Documentation

The documentation for rust goes with its libraries. Especially as they are not part of rust, the quality of documentation varies. Coming from more of an open source community, there seems to be less of an emphasis on documentation in rust, where Microsoft has always provided high quality documentation for all of .NET and the external libraries that it supplies.

Rust suggests that all developers read the same book. There are far more books available about .NET, and blog posts, documentation, communities, and otherwise.

Developer Community

I find helpful developers in both rust and C# communities, but my perception is that the .NET community is much larger and more interactive outside of code and tools like github.

Support

While Microsoft may support .NET for some customers, real support comes from the community, which is larger. Rust does not have an equivalent of Microsoft behind it; support comes from the tool, documentation, library, and other maintainers in the smaller community.

Coding Conventions

While much of the syntax is relatively similar, coding conventions are slightly different between C# and rust. C# developers tend to make greater use of whitespace, specifically in the form of curly braces.

C# developers typically store one significant type per source code file, where rust convention often includes multiple types in a single file. Each approach has advantages and disadvantages, but rust generally makes it more cumbersome to separate types into different files.

Rust tooling such as rustfmt enforces rigid coding conventions, especially around the use of whitespace but in general to reduce the number of tokens required to express logic in code. Using tools like rustfmt increases code consistency, which increases readability between developers and development teams. Similar tooling may be available for .NET, but there is no tools to enforce a universal standard.

Maturity

C# is about 20 years old and has roots in C and C++ that go back decades before that. Rust is younger but has similar roots. I believe that C# has completed most of its growing pains while rust is just approaching them.

Productivity

I believe that qualified C# and rust developers can be equally productive, although I think that the libraries provided by and available for .NET may allow greater product delivery.

Code Safety and Quality Enforcement

Just as with any systems language, it is possible and sometimes necessary to do potentially unsafe things in rust. An operating system kernel written in rust would almost certainly resort to assembly language for key components. Even without allowances for unsafe features, rust itself has not been proven to be completely safe, and the tooling and library management systems are both arguably more vulnerable than those provide for .NET. As a program cannot and should not trap every possible error, the potential for rust programs to panic (crash and terminate) may not always be the best response to every error condition, which could lead to challenging error management conditions for developers. So rust is neither perfect nor proven. At the same time, I think that the way that rust manages memory and its tooling do a better job of ensuring code safety and enforcing code quality. It requires developers to think more about what the machine is doing rather than what their abstractions are doing, which means smarter developers, which should mean better code. I know this paragraph is jumbled but this is a complex and subjective topic. It’s hard to explain, but in my opinion, rust wins.

Where is Each Appropriate?

I must admit that C# has always been my favorite language, but I am starting to lean towards rust when it feels more appropriate. I feel that .NET can do anything, but ASP.NET Core is where it provides the greatest value, which means web applications and services. Rust would probably be good for services as well and could certainly support web applications, though possibly not with an architecture as strong as ASP.NET MVC.

I feel that rust is more appropriate for developers who prefer Linux, especially those that focus on command line tools and Linux itself, and is superior where performance and memory management are most critical, as well as (arguably) code safety. Based primarily on the library situation, I think that it would be much easier to implement significant applications in .NET than in rust.

I think of ASP.NET as a platform. I think of rust as processes, or even at a lower level in device drivers and embedded systems, although using efficient techniques for web applications would have significant advantages. Unfortunately, .NET has the libraries and rust has the language implementation. I cannot see building a platform with rust at this time.

Especially if rust can address some of the issues around libraries before a competing technology arrives, and before someone dares to write an operating system kernel in Jamstack (which I fear may be coming next) and takes down global infrastructure, I think that both technologies have strong futures.

Updates

Excluding the conclusion, this section and the following section contains updates made after I wrote the original draft.

Debugging

I generally do not use debuggers, but I know that the Visual Studio debugger is incredible, and I doubt that equivalent tooling will exist for rust in the short term. Again, I have not tried rust extensions for Visual Studio Code.

Even without much more experience with .NET, I think that .NET stack traces are more straightforward to read than rust backtraces. Unlike .NET, and like for security reasons, backtraces do not appear by default, so often when a crash occurs, information about its occurrence is lost by default. While I understand how stack trace and back traces could be useful to attackers, recording their occurrence is critical both to being aware of the attack and resolving the root issue. Maybe rust could default to enabling backtraces only for storage locally in a file system that administrators could monitor to detect and address issues. There are several approaches to these types of problems with .NET, which I consider preferable in this case.

Disassembly

Several tools are available for disassembling .NET libraries and programs into an approximation of their source code, and even stepping through that source code in debuggers, which can be unbelievably convenient. Equivalent tooling is not available for rust, which requires that developers fall back on documentation and source code. Both approaches are good for learning not just existing APIs and programming techniques, but disassembly is much more effective and the primary way that I learned C#, especially as the compiler can optimize code in ways that suggest different coding techniques.

Concurrency

I like the .NET support for threading, but using it requires some awareness, understanding, and programming, and using it incorrectly can be dangerous, especially when the developer is not aware that it is in use. Much of rust is theoretically threadsafe by default without requiring any consideration by the developer and possibly without their knowledge, as explicit code for threading is often unnecessary. In combination with its raw performance and memory management, which can be critical for concurrent logic, as well as certain programming paradigms, rust wins.

I Should Really Do Some Research Before I Just Start Writing

Conclusion

This post got long even though I probably forgot about half of what I had intended to write. If you have an thoughts on this topic, please comment on this blog post.

2 thoughts on “Comparing Rust and C#

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: