One issues I have had with Rust applications is the huge binary size (yes, I know this has improved a bit lately). Is there a good comparison between kernel C and kernel Rust code in this regard?
This talks about going to extreme lengths on making the smallest Rust binary possible, 400 bytes when it was written, https://darkcoding.net/software/a-very-small-rust-binary-ind...
The thing is, you lose a lot of nice features when you do this, like panic unwinding, debug symbols, stdlib… for kernel and some embedded development it’s definitely important, but for most use cases, does it matter?
In ye olden days it was common to distribute a binary without debug symbols, but to keep a copy of them for every released build¹. If an application crashed (panicked, signalled, etc.) you got a core dump that you could debug using the stripped binary together with the symbol file. This gave you both smaller binary sizes and full debugging capability at the cost of some extra administration. I'm not sure if this is possible with "stock" Rust, but if you need lean binaries but want to do forensic investigation it's something to look into.
1. https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separ...
At that point I suspect the biggest culprits are overuse of monomorphisation, and often just more stuff happening compared to equivalent C++ code because the language makes larger code bases more maintainable. I'd also count some niceties in that category like better string formatting or panic handling, which is an insignificant cost in any larger software but appears big in tiny hello-world type programs.
Turn off the standard library and your binaries can be incredibly small. This is how it’s used in microcontrollers and the Linux Kernel doesn’t use the full standard library either.
However, most complaints are about size of “Hello World”, which in Rust is due to libstd always having debug info (to be fixed soon), and panic handling code that includes backtrace printing (because print to stdout can fail).
Printing of backtrace is very bloaty, because it parses and decompresses debug info.
FWIW, you can configure this in Cargo.toml:
[profile.release] strip = true
That change will be live on 21st March, so manual strips won't be required after that.
Can you quantify this? How big is too big? Ideally for a real program, and not an experiment to make the tiniest possible program.
On my Windows machine ripgrep rg.exe is just 4.2mb. Making that smaller feels irrelevant.
I’m not convinced that binary size is a real problem. But I’m open to evidence!
I've only used rust nightly for my own projects and didn't give too much thought about rust versions
And you can use clippy to tell you about changes you should make.
For example, in my projects I run this in the CI pipeline:
cargo clippy --all-targets --all-features
and cargo fmt --all --check
In addition to the regular test and build steps.This both means that I follow clippy recommendations and cargo fmt in the first place, and also that my CI tells me about any clippy changes if I didn’t notice them myself as well as any formatting I’m not following. In my main IDE I auto format the code of course. But sometimes I make small changes in vim and don’t run the format step myself so it’s nice to have for that reason as well.
For the integration of Rust into the Linux kernel I imagine it’s a bit more convoluted.
> I estimate it's about ½ hour per 1 million lines, on average.
That being said, Rust for Linux isn't using stable Rust, so they have a higher burden than projects that do.
If you include dependencies then it can happen that a dependency relies on unstable features. In which case you might have to upgrade the library version (if they support the new compiler version). The library might have changed the API by then which would force you to change your code.
Except for the above use case, upgrades to the latest version of the compiler have been painless for me.
> If any of the following conditions are violated, the result is Undefined Behavior:
> * Both the starting and resulting pointer must be either in bounds or one byte past the end of the same allocated object.
> * The computed offset cannot exceed isize::MAX bytes.
> * The offset being in bounds cannot rely on “wrapping around” the address space. That is, the infinite-precision sum must fit in a usize.
> Most platforms fundamentally can’t even construct such an allocation. For instance, no known 64-bit platform can ever serve a request for 263 bytes due to page-table limitations or splitting the address space. However, some 32-bit and 16-bit platforms may successfully serve a request for more than isize::MAX bytes with things like Physical Address Extension. As such, memory acquired directly from allocators or memory mapped files may be too large to handle with this function.
> Consider using wrapping_sub instead if these constraints are difficult to satisfy. The only advantage of this method is that it enables more aggressive compiler optimizations.
If their pointer subtraction uses similar semantics, there might be issues if they want to compare pointers from different allocation objects, are worried about 32-bit or 16-bit platforms, or maybe even consider the performance concerns too worrisome. The Rust I write tends to be a bit higher-level and doesn't require unsafe, so my instinctual reaction to "using unsafe for performance" generally errs on the same of abject terror, but it's a fundamental part of what makes the safe side of abstractions I use possible, and the kernel is probably one of those places that needs to do that sometimes, so I'd reluctantly have to admit I'm probably not qualified to evaluate whether these cases would merit unsafety for performance alone, but the first two concerns sound like legitimate things that the kernel would need to handle.
Rust's standard library has three elements
core has stuff you get with the Rust language, like any use of Rust, Rust for Linux has core. You could technically implement Rust without core, or at least, without most of it, but that's not really the Rust language, you've instead made your own weird fork.
[T]::sort_unstable() is a core function which sorts a slice of some Ordered type T but may re-arrange elements despite them comparing equal hence the word "unstable".
alloc depends on an allocator. You may not have an allocator, e.g. you're a tiny embedded controller, in which case you likely don't want and can't use this. Rust for Linux re-implements alloc, basically cloning the "official" alloc and fiddling with it.
Vec::try_reserve() is a feature found in alloc, it tries to allocate enough space to ensure your Vec has a certain amount of capacity beyond its current size, and if not reports it could not.
std further depends on an Operating System, it offers exciting features like knowing what the time is, reading a file, connecting to a remote service over TCP/IP, or making a thread. Rust for Linux does not provide std.
File::create() is a std function which creates files.
The function you were interested in is part of core (although you were looking at its re-export from std) and so yes, it exists in Rust for Linux.
Any of these type of issue would equally invalidate using unsafe{} to cast the pointer to a reference, which is what non_null! does.
C: 33,351,596 lines
(That's just doing 'wc -l' rather than using any proper code metrics tool)
===============================================================================
Language Files Lines Code Comments Blanks
===============================================================================
C 33553 23772322 17694564 2662642 3415116
C Header 24554 9562920 7395591 1436546 730783
Device Tree 5041 1512839 1240129 76384 196326
ReStructuredText 3473 711669 539971 0 171698
JSON 788 443098 443096 0 2
YAML 3905 433860 352107 16626 65127
GNU Style Assembly 1317 372131 271873 55613 44645
Shell 894 172036 120033 21590 30413
Plain Text 1739 151033 0 123992 27041
Makefile 2946 76889 52985 12355 11549
Python 203 68545 54627 4452 9466
SVG 74 49420 48159 1171 90
Perl 59 43992 34124 4074 5794
Happy 10 6069 5359 0 710
Assembly 5 3319 3065 0 254
C++ 5 2138 1860 61 217
BASH 59 1943 1318 335 290
Unreal Script 5 707 445 158 104
ASN.1 16 660 445 87 128
Autoconf 5 429 373 26 30
LD Script 8 376 288 29 59
CSS 3 295 172 69 54
Gherkin (Cucumber) 1 291 199 58 34
TeX 1 236 156 74 6
XSL 10 200 122 52 26
HEX 2 173 173 0 0
Module-Definition 2 128 113 0 15
C++ Header 2 125 59 55 11
RPM Specfile 1 108 93 1 14
Objective-C 1 89 72 0 17
Vim script 1 42 33 6 3
Markdown 1 36 0 27 9
Automake 3 31 23 3 5
Ruby 1 29 25 0 4
INI 2 13 6 5 2
TOML 1 12 2 9 1
Apache Velocity 1 12 12 0 0
CMake 2 8 8 0 0
-------------------------------------------------------------------------------
Rust 64 12637 9489 1612 1536
|- Markdown 55 8243 808 5557 1878
(Total) 20880 10297 7169 3414
-------------------------------------------------------------------------------
HTML 2 28 22 3 3
|- JavaScript 1 7 7 0 0
(Total) 35 29 3 3
===============================================================================
Total 78760 37400888 28271191 4418115 4711582
===============================================================================
So If we would only count code and not comments, it is only 9489 LoC Rust. Which would be about 0.03% and if we take all lines and not only LoC it would be around 0.05%[0] https://github.com/XAMPPRocky/tokei
[1] https://github.com/torvalds/linux/commit/b401b621758e46812da...
What are the implications of using Rust on building Linux?