```#![no_std]``` for ordinary apps

Hello everyone!

Recently, I was experimenting with creating a CLI #![no_std] programs (built a hello-world program in no-std mode). Though no-std mode is usually set for cases where std is not available, such as operating system kernel, an ordinary app may choose to opt out of using std as well. What I've noticed:

  • Since there is no std to handle start-up anymore, an app has to set #![no_main] and manually define a main() function, like:
fn main(argc: core::ffi::c_int, argv: *mut *mut core::ffi::c_char) -> core::ffi::c_int

Windows apps may have to use fn wmain() with wchar_t argument

  • An app may have to disable unwinding panics with panic=abort option for profiles and building with -Zbuild-std. Also, a panic handler must be defined
  • #![no_std] also disables alloc, but it can be re-enabled with extern crate alloc;. As far as I understand, alloc is merely a high-level interface to the memory allocator, it doesn't allocate memory itself (and if a crate uses it without std, it has to define a global allocator)
  • libc is not automatically linked, and the crate may fail to build due to missing symbols on which core depends -- it can be solved by adding compiler_builtins crate

I wonder, is there any benefit for apps that can use std, opt out of it? As far as I understand, there may be several benefits:

  • More control over start-up and shut-down process of an application, as well as environment and command-line argument processing
  • More control over memory allocation
  • Being able to opt-out of infallible memory allocation completely (however, as far as I understand, it can require either replacing alloc with your own interface and, as a result, losing the ability to use crates that depend on alloc, or enabling no_global_oom_handling option)
  • Smaller binary size (I've built two "hello, world" programs -- one with std and one without it, the ordinary one in --release mode resulted in a 437 kilobytes binary while no-std one resulted in a 8 kilobytes binary)
1 Like

Well, you can always write Rust as if it were C. Used no_std. Use libc for everything you need from printf() upwards instead. Implement your own dynamic arrays (Vectors) etc. Just like the C folks do. If the borrow checker annoys you make everything "unsafe" and use raw pointers instead of references. Go nuts!

For a brilliant example of this C style Rust (Crust) coding have a look at
"What if Rust was Worse than C" https://www.youtube.com/watch?v=5MIsMbFjvkw&t=3627s

More brilliantly Tsoding, the maker of that video, goes on to write an entire compiler for the B language in Rust in the Crust style.

Probably a good idea to use Miri and as many analysers as you can to save you from going crazy!

It's a lot of fun.

Use libc for everything you need from printf() upwards instead.

I don't see any reason to use printf() instead of Rust formatting system. In my "hello, world", I was using core::fmt::Write wrapper over write() function (on Linux).

It's a lot of fun.

"C-style" Rust may be a lot of fun (like code golfing), but it's not practical and may be harder to maintain than idiomatic Rust. When I was experimenting with #![no_std] in ordinary apps, I was trying to find practical uses of it (such as building a small binary that only includes what's necessary).

I certainly would not seriously recommend no-std and/or the "Crust" style for most desktop/server developers. "ordinary apps" as you say.

no-std is essential though for kernel development or small embedded systems and so on. I have worked on Linux systems that were small enough that using their libc in applications instead of hauling in the Rust standard library would have been essential.

Rust presents itself as a "systems programming language" which to my mind means you can use it to write systems like kernels from scratch. So I'm happy that there is no-std a way to write Rust as if it were C.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.