Why does the rust startup routine bloat both stack and heap for apparently no reason?

A C programme, as far as I can tell, does not use stack nor heap unless you explicitly put stuff there. The heap is only used if there is an explicit call to malloc/etc within the code you write/link to. It appears that no matter how simple, though, a Rust program always calls malloc and affiliated around nine times. Even the Hello World program makes some malloc calls. See for yourself :

Memory usage summary: heap total: 3404, heap peak: 1616, stack peak: 1072
         total calls   total memory   failed calls
 malloc|          6           3188              0
realloc|          2             64              0  (nomove:0, dec:0, free:0)
 calloc|          1            152              0
   free|          8           2860
Histogram for block sizes:
    0-15              1  11% =========================
   32-47              2  22% ==================================================
  112-127             1  11% =========================
  144-159             1  11% =========================
  464-479             1  11% =========================
  544-559             1  11% =========================
 1024-1039            2  22% ==================================================

This is the output memusage generates when it checks the hello world program as in its simplest implementation. I get it that computers are fast and leaks are memory safe but... seriously ? What is that ? Does linking to the standard library force you to have at least one memory leak no matter how cautious you are ? Calls to malloc and friends aren't the fastest and I don't see why the rust startup routine uses so much memory and does so many allocations where a C program can do the exact same without that overhead. Anyone knows why this choice was made ?

My guess is panic hook machinery, TLS, that sort of thing.

You can use no_std if you want to avoid pulling in runtime stuff, but, uh, it's not super nice: 【Rust】"Hello world!" without Standard Library

Rust has a small runtime (in fact, even C has) that runs code before and after main() (rust/library/std/src/rt.rs at daf2e5e18b3abc4311d9da3d96fd64138f43d508 · rust-lang/rust · GitHub), and println!() also has non-trivial implementation, including e.g. locking.

Because it doesn't matter. Seriously, it just does not. The impact on startup time is basically zero for a few allocations, and if you want (e.g. for bare-metal), you can disable the runtime by disabling the standard library.

No worries. Try this:

#![no_std]
#![no_main]

// Import the external C printf function from the C standard library
unsafe extern "C" {
    pub fn printf(format: *const core::ffi::c_char, ...) -> core::ffi::c_int;
}

// The entry point of the program, overriding the standard main function
#[no_mangle]
pub extern "C" fn main()  {
    let number : core::ffi::c_int = 42;
    unsafe {
        // C strings must end with a null terminator (\0)
        printf(b"Hello, World! %d\n\0".as_ptr() as *const core::ffi::c_char, number);
    }
}

// A panic handler is mandatory when #![no_std] is active
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

Compile and run:

rustc -C panic=abort -C link-args="-lSystem" main.rs

➜  ~ ./main                                              
Hello, World! 42

The magical part of this is that you can have variadic functions in Rust if you use the FFI which otherwise you cannot.

Not today, but soon you'll be able to!

Writing an entire C program in Rust to restrict memory usage to the CRT is definitely funny-looking haha, this made my day : )

I think my main complain here is about the leak. The Rust standard runtime seems to always leak the last allocation it makes, which makes valgrind and memusage output ugly. It doesn't help that these allocations don't seem consistent (around 9 but not guaranteed, and stack + heap usage is uncertain), which makes the whole thing even uglier. It is perfectly understandable to not free the last allocation in your program since you know the OS is gonna do it for you at process termination, but I wholly believe this should not be considered good practice, and even less so in the standard runtime...

Glad to bring some joy to the world. It's called Crust :slight_smile:

What I was wondering though is if that Crust example also shows the leak or not.

It would be nice if Rust did clean up after itself. I was once running someone's C++ program compiled to asm.js in the browser. Problem was every time it ran it ate some memory. Luckily the author was kind enough to add the clean up to his code.

I've got some answers for you. I've run your program and it turns out Crust still has one unseriously managed allocation ! Here's the memusage output :

Memory usage summary: heap total: 1024, heap peak: 1024, stack peak: 1216
         total calls   total memory   failed calls
 malloc|          1           1024              0
realloc|          0              0              0  (nomove:0, dec:0, free:0)
 calloc|          0              0              0
   free|          0              0
Histogram for block sizes:
 1024-1039            1 100% ==================================================

I say "unseriously managed" because running valgrind on the same program showed that, in the valgrind runtime, the memory does indeed get freed. It seems that the memory freeings are quite random, and I can't figure out the purpose of that heap allocation. Notice the heavy stack usage too !

Hello, World! 42
==20903== 
==20903== HEAP SUMMARY:
==20903==     in use at exit: 0 bytes in 0 blocks
==20903==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==20903== 
==20903== All heap blocks were freed -- no leaks are possible
==20903== 
==20903== For lists of detected and suppressed errors, rerun with: -s
==20903== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Zero chances of that happening. Even if thre are lots of people who want this (mostly the ones who want hot reloading, which is more than just the ability to clean up after itself, but implies that).

This would mean there are 'static variables. No 'static lifetime, in general, most likely. The whole ecosystem would need to be radically different.

Not impossible, but so much outside of what is possible in std and no_std modes that chances of that happening are essentially zero.

Note that problem is not the language, language itself can be adapted relatively easily. But this would need a total overhaul of the whole ecosystem which is unlikely to happen without extremely compelling reason.

And I fail to see what that reason can be.

I'm pretty sure they were referring to the Rust runtime, and it can happen, if it will be easy enough. Clean valgrind reports (even though this is a bug in Valgrind that reports reachable allocations as leaked, Miri does not do this) are a worthy goal.

Not even remotely close. I was referreing to the fact that Rust is memory safe language and that memory safety relies, among other things, on the ability to guarantee that objects with lifetime 'static are never deallocated. It's not hard to make it non-'static, we may name that lifetime 'module, but the cost wouldn't be the need to change Rust compiler or runtime, the cost would be in the need to rewrite uncountable amount of code that relies of static objects to have 'static lifetime.

Worthy enough to start grandiose rewrite of pretty much the whole Rust ecosystem from the ground up?

Note that Rust is far from being unique here. In C++ the rule “if you made a mistake of putting object in a global object without properly ensuring it would become “a memory leak” then you should go and fix that mistake” is simply enforced on reviews (often there are CI job that ensures that no one tries to be clever foolish enoush to chase clean valgrind reports).

Because “clean valgrind reports” and “robust software that doesn't crash when it tries to send crash report” are very much in conflict… and Valgrind clarity only wins in toy projects.

As the original poster, I would like to add a little thing here. I was indeed talking about the Rust runtime in my initial post, and all answers are related to that. So, the "not even close" feels a bit far fetched, or you are taking personally a comment which was referring to the original topic.

While you point about 'static lifetimes is interesting, it does not change my original point by much : it would be very nice if the Standard Rust Runtime could clean after itself and not leave memory leaks caused by unpredictable uses of C malloc. While a clean Valgrind output might be foolish to chase in a large codebase, it would be great indeed to be able to have one in a toy example.

In all honesty, I don't know why the Rust runtime uses C malloc at all. The C runtime does just fine without using malloc for the exact same Hello World! program. And even if the Rust runtime somehow needed non-freeable essential allocations, then why not just use static buffers instead of malloc ? Malloc return pointers do not have 'static lifetime by default, and it I don't think it's outrageous to question the fact that the runtime itself apparently can't be bothered to use free consistently on its already peculiar heap allocations !

I was not talking about you. I wrote "they" in response to you, and meant @Kifaninja.

That phrase doesn't make any sense. Pointers don't have lifetime, references do. And making 'static reference from pointer returned by malloc is trivial and safe.

Why shouldn't it do that?

Because using malloc is easier?

You are asking why Rust uses simple, obvious and trivially correct solution instead of complicated, non-trivial and convoluted solution.

Normally such things are not considred “outrageous”, but they still need some kind of justification.

People tend to use simple solutions when they work — and by “simple” I mean “amount of thought and effort needed to implement them”, not “number of bytes used by them”.

Complicated solutions need a justification, simple ones don't… that's the normal logic, isn't it?

I would say it changes things pretty radically. In some kind of alternate universe where Rust doesn't have static variables with 'static lifetime, where modules may be unload at will, where hot reload is the king it would strange and unusual for the runtime to use malloc without correctly freeing that memory with free. Precisely because everyone else would need to do that and runtime library would be a weird exception.

In our world it imposes limitation that is not imposed on any other module… why? What would it provide and for whom? The moment you would try to use crate that relies on static being 'static you would lose your pristive valgrind output anyway… and if that's the not the goal that was is the goal? Why should runtime stop using useful tool if it's not providing real benefits to anyone?

Maybe whatever tool you use to get these statistics shows them before thread local destructors get run while valgrind shows it at the point of the exit_group syscall that finally ends the process? There is a thread local allocation for the thread info data containing the location of the end of the stack (for stack overflow reporting) and the thread name (for panic messages) I'm not sure, but I think that allocation gets destroyed by a TLS destructor.

We do in fact make some attempt at this, but this actually does introduce bugs. For example we cleanup the stackoverflow handler and free the associated sigaltstack, but this causes us to report stack overflows in TLS destructors as SIGSEGV rather than with a stack overflow message:

FYI, a simple C puts("Hello, World!"); compiled with GCC and -O2 on my machine results in the same 1024 byte "leak".

So, stack usage was 1216 bytes heavy, where these days the AVX-512 register set alone counts 2048 bytes?

While "good practice" is highly subjective, even quite the opposite is true, I would argue:

There are a lot of good reason to use all the different .leak() methods on various std types, like Box, String, Vec, etc. for data that stays constant for the remainder of the program’s life.

One could even argue, while the OS is releasing all the allocated resources on exit, why bother to waste unnecessary CPU time by letting the allocator juggle its data structures? The OS needs to remove the virtual memory mappings anyway.

For practical reasons, the definition of a memory leak is not the absence of a 1:1 correspondence between malloc and free, but rather whether memory usage increases steadily during runtime, such that memory is exhausted if the programme is simply left to run for long enough.

Maybe, the buffers size or count is not known at compile time?

Honestly, judging your wording, I have the impression that you primary interest on this issue are not the technical reasons.

Let's take a closer look. If you run gdb on this program:

fn main() {}

and set a breakpoint on __GI___libc_malloc, you can see exactly from where the malloc() calls are made.

$ rustc -g main.rs
$ gdb main # or rust-gdb
Reading symbols from main...
(gdb) break __GI___libc_malloc
Function "__GI___libc_malloc" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (__GI___libc_malloc) pending.
(gdb) commands
>backtrace
>continue
>end
(gdb) run
Starting program: main
Breakpoint 1, __GI___libc_malloc (bytes=bytes@entry=472) at ./malloc/malloc.c:3294
#0  __GI___libc_malloc (bytes=bytes@entry=472) at ./malloc/malloc.c:3294
#1  0x00007ffff7df3e7f in __fopen_internal (is32=1, mode=0x7ffff7f38db4 "rce", filename=0x7ffff7f3a42d "/proc/self/maps") at ./libio/iofopen.c:65
#2  _IO_new_fopen (filename=filename@entry=0x7ffff7f3a42d "/proc/self/maps", mode=mode@entry=0x7ffff7f38db4 "rce") at ./libio/iofopen.c:86
#3  0x00007ffff7e0c0b6 in __pthread_getattr_np (thread_id=140737351432064, attr=0x7fffffffe360) at ./nptl/pthread_getattr_np.c:85
#4  0x000055555558cc0e in std::sys::pal::unix::stack_overflow::imp::get_stack_start () at library/std/src/sys/pal/unix/stack_overflow.rs:372
#5  std::sys::pal::unix::stack_overflow::imp::stack_start_aligned () at library/std/src/sys/pal/unix/stack_overflow.rs:389
#6  std::sys::pal::unix::stack_overflow::imp::install_main_guard_linux () at library/std/src/sys/pal/unix/stack_overflow.rs:442
#7  std::sys::pal::unix::stack_overflow::imp::install_main_guard () at library/std/src/sys/pal/unix/stack_overflow.rs:413
#8  std::sys::pal::unix::stack_overflow::imp::init () at library/std/src/sys/pal/unix/stack_overflow.rs:155
#9  std::sys::pal::unix::init () at library/std/src/sys/pal/unix/mod.rs:41
#10 std::rt::init () at library/std/src/rt.rs:118
#11 std::rt::lang_start_internal::{closure#0} () at library/std/src/rt.rs:173
#12 std::panicking::catch_unwind::do_call<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panicking.rs:581
#13 std::panicking::catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> () at library/std/src/panicking.rs:544
#14 std::panic::catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panic.rs:359
#15 std::rt::lang_start_internal () at library/std/src/rt.rs:171
#16 0x0000555555567957 in std::rt::lang_start<std::process::ExitCode> (main=0x555555567a20 <main::main>, argc=1, argv=0x7fffffffe5c8, sigpipe=0) at /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205
#17 0x0000555555567a5e in main ()
#18 0x00007ffff7d981ca in __libc_start_call_main (main=main@entry=0x555555567a40 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe5c8) at ../sysdeps/nptl/libc_start_call_main.h:58
#19 0x00007ffff7d9828b in __libc_start_main_impl (main=0x555555567a40 <main>, argc=1, argv=0x7fffffffe5c8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe5b8) at ../csu/libc-start.c:360
#20 0x0000555555567855 in _start ()
Click to expand the rest of the output
Breakpoint 1, __GI___libc_malloc (bytes=bytes@entry=120) at ./malloc/malloc.c:3294
3294    in ./malloc/malloc.c
#0  __GI___libc_malloc (bytes=bytes@entry=120) at ./malloc/malloc.c:3294
#1  0x00007ffff7df4d85 in __GI___getdelim (lineptr=lineptr@entry=0x7fffffffe2e0, n=n@entry=0x7fffffffe2e8, delimiter=delimiter@entry=10, fp=fp@entry=0x5555555aa2a0) at ./libio/iogetdelim.c:65
#2  0x00007ffff7dcd7c1 in __getline (lineptr=lineptr@entry=0x7fffffffe2e0, n=n@entry=0x7fffffffe2e8, stream=stream@entry=0x5555555aa2a0) at ./stdio-common/getline.c:28
#3  0x00007ffff7e0c20f in __pthread_getattr_np (thread_id=140737351432064, attr=0x7fffffffe360) at ./nptl/pthread_getattr_np.c:122
#4  0x000055555558cc0e in std::sys::pal::unix::stack_overflow::imp::get_stack_start () at library/std/src/sys/pal/unix/stack_overflow.rs:372
#5  std::sys::pal::unix::stack_overflow::imp::stack_start_aligned () at library/std/src/sys/pal/unix/stack_overflow.rs:389
#6  std::sys::pal::unix::stack_overflow::imp::install_main_guard_linux () at library/std/src/sys/pal/unix/stack_overflow.rs:442
#7  std::sys::pal::unix::stack_overflow::imp::install_main_guard () at library/std/src/sys/pal/unix/stack_overflow.rs:413
#8  std::sys::pal::unix::stack_overflow::imp::init () at library/std/src/sys/pal/unix/stack_overflow.rs:155
#9  std::sys::pal::unix::init () at library/std/src/sys/pal/unix/mod.rs:41
#10 std::rt::init () at library/std/src/rt.rs:118
#11 std::rt::lang_start_internal::{closure#0} () at library/std/src/rt.rs:173
#12 std::panicking::catch_unwind::do_call<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panicking.rs:581
#13 std::panicking::catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> () at library/std/src/panicking.rs:544
#14 std::panic::catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panic.rs:359
#15 std::rt::lang_start_internal () at library/std/src/rt.rs:171
#16 0x0000555555567957 in std::rt::lang_start<std::process::ExitCode> (main=0x555555567a20 <main::main>, argc=1, argv=0x7fffffffe5c8, sigpipe=0)
    at /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205
#17 0x0000555555567a5e in main ()
#18 0x00007ffff7d981ca in __libc_start_call_main (main=main@entry=0x555555567a40 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe5c8) at ../sysdeps/nptl/libc_start_call_main.h:58
#19 0x00007ffff7d9828b in __libc_start_main_impl (main=0x555555567a40 <main>, argc=1, argv=0x7fffffffe5c8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe5b8) at ../csu/libc-start.c:360
#20 0x0000555555567855 in _start ()

Breakpoint 1, __GI___libc_malloc (bytes=bytes@entry=1024) at ./malloc/malloc.c:3294
3294    in ./malloc/malloc.c
#0  __GI___libc_malloc (bytes=bytes@entry=1024) at ./malloc/malloc.c:3294
#1  0x00007ffff7df31b5 in __GI__IO_file_doallocate (fp=0x5555555aa2a0) at ./libio/filedoalloc.c:101
#2  0x00007ffff7e03524 in __GI__IO_doallocbuf (fp=fp@entry=0x5555555aa2a0) at ./libio/libioP.h:1030
#3  0x00007ffff7e00884 in _IO_new_file_underflow (fp=0x5555555aa2a0) at ./libio/fileops.c:486
#4  0x00007ffff7df4e10 in __GI___getdelim (lineptr=lineptr@entry=0x7fffffffe2e0, n=n@entry=0x7fffffffe2e8, delimiter=delimiter@entry=10, fp=fp@entry=0x5555555aa2a0) at ./libio/iogetdelim.c:77
#5  0x00007ffff7dcd7c1 in __getline (lineptr=lineptr@entry=0x7fffffffe2e0, n=n@entry=0x7fffffffe2e8, stream=stream@entry=0x5555555aa2a0) at ./stdio-common/getline.c:28
#6  0x00007ffff7e0c20f in __pthread_getattr_np (thread_id=140737351432064, attr=0x7fffffffe360) at ./nptl/pthread_getattr_np.c:122
#7  0x000055555558cc0e in std::sys::pal::unix::stack_overflow::imp::get_stack_start () at library/std/src/sys/pal/unix/stack_overflow.rs:372
#8  std::sys::pal::unix::stack_overflow::imp::stack_start_aligned () at library/std/src/sys/pal/unix/stack_overflow.rs:389
#9  std::sys::pal::unix::stack_overflow::imp::install_main_guard_linux () at library/std/src/sys/pal/unix/stack_overflow.rs:442
#10 std::sys::pal::unix::stack_overflow::imp::install_main_guard () at library/std/src/sys/pal/unix/stack_overflow.rs:413
#11 std::sys::pal::unix::stack_overflow::imp::init () at library/std/src/sys/pal/unix/stack_overflow.rs:155
#12 std::sys::pal::unix::init () at library/std/src/sys/pal/unix/mod.rs:41
#13 std::rt::init () at library/std/src/rt.rs:118
#14 std::rt::lang_start_internal::{closure#0} () at library/std/src/rt.rs:173
#15 std::panicking::catch_unwind::do_call<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panicking.rs:581
#16 std::panicking::catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> () at library/std/src/panicking.rs:544
#17 std::panic::catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panic.rs:359
#18 std::rt::lang_start_internal () at library/std/src/rt.rs:171
#19 0x0000555555567957 in std::rt::lang_start<std::process::ExitCode> (main=0x555555567a20 <main::main>, argc=1, argv=0x7fffffffe5c8, sigpipe=0)
    at /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205
#20 0x0000555555567a5e in main ()
#21 0x00007ffff7d981ca in __libc_start_call_main (main=main@entry=0x555555567a40 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe5c8) at ../sysdeps/nptl/libc_start_call_main.h:58
#22 0x00007ffff7d9828b in __libc_start_main_impl (main=0x555555567a40 <main>, argc=1, argv=0x7fffffffe5c8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe5b8) at ../csu/libc-start.c:360
#23 0x0000555555567855 in _start ()

Breakpoint 1, __GI___libc_malloc (bytes=bytes@entry=32) at ./malloc/malloc.c:3294
3294    in ./malloc/malloc.c
#0  __GI___libc_malloc (bytes=bytes@entry=32) at ./malloc/malloc.c:3294
#1  0x00007ffff7e1c10c in __GI___libc_realloc (oldmem=oldmem@entry=0x0, bytes=bytes@entry=32) at ./malloc/malloc.c:3425
#2  0x00007ffff7e0bf7a in __pthread_getattr_np (thread_id=140737351432064, attr=0x7fffffffe360) at ./nptl/pthread_getattr_np.c:180
#3  0x000055555558cc0e in std::sys::pal::unix::stack_overflow::imp::get_stack_start () at library/std/src/sys/pal/unix/stack_overflow.rs:372
#4  std::sys::pal::unix::stack_overflow::imp::stack_start_aligned () at library/std/src/sys/pal/unix/stack_overflow.rs:389
#5  std::sys::pal::unix::stack_overflow::imp::install_main_guard_linux () at library/std/src/sys/pal/unix/stack_overflow.rs:442
#6  std::sys::pal::unix::stack_overflow::imp::install_main_guard () at library/std/src/sys/pal/unix/stack_overflow.rs:413
#7  std::sys::pal::unix::stack_overflow::imp::init () at library/std/src/sys/pal/unix/stack_overflow.rs:155
#8  std::sys::pal::unix::init () at library/std/src/sys/pal/unix/mod.rs:41
#9  std::rt::init () at library/std/src/rt.rs:118
#10 std::rt::lang_start_internal::{closure#0} () at library/std/src/rt.rs:173
#11 std::panicking::catch_unwind::do_call<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panicking.rs:581
#12 std::panicking::catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> () at library/std/src/panicking.rs:544
#13 std::panic::catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panic.rs:359
#14 std::rt::lang_start_internal () at library/std/src/rt.rs:171
#15 0x0000555555567957 in std::rt::lang_start<std::process::ExitCode> (main=0x555555567a20 <main::main>, argc=1, argv=0x7fffffffe5c8, sigpipe=0)
    at /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205
#16 0x0000555555567a5e in main ()
#17 0x00007ffff7d981ca in __libc_start_call_main (main=main@entry=0x555555567a40 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe5c8) at ../sysdeps/nptl/libc_start_call_main.h:58
#18 0x00007ffff7d9828b in __libc_start_main_impl (main=0x555555567a40 <main>, argc=1, argv=0x7fffffffe5c8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe5b8) at ../csu/libc-start.c:360
#19 0x0000555555567855 in _start ()

Breakpoint 1, __GI___libc_malloc (bytes=bytes@entry=32) at ./malloc/malloc.c:3294
3294    in ./malloc/malloc.c
#0  __GI___libc_malloc (bytes=bytes@entry=32) at ./malloc/malloc.c:3294
#1  0x00007ffff7e1c10c in __GI___libc_realloc (oldmem=<optimized out>, bytes=bytes@entry=32) at ./malloc/malloc.c:3425
#2  0x00007ffff7e081d9 in __GI___pthread_attr_setaffinity_np (attr=attr@entry=0x7fffffffe360, cpusetsize=cpusetsize@entry=32, cpuset=cpuset@entry=0x5555555aaa10)
    at ./nptl/pthread_attr_setaffinity.c:51
#3  0x00007ffff7e0c008 in __pthread_getattr_np (thread_id=<optimized out>, attr=0x7fffffffe360) at ./nptl/pthread_getattr_np.c:194
#4  0x000055555558cc0e in std::sys::pal::unix::stack_overflow::imp::get_stack_start () at library/std/src/sys/pal/unix/stack_overflow.rs:372
#5  std::sys::pal::unix::stack_overflow::imp::stack_start_aligned () at library/std/src/sys/pal/unix/stack_overflow.rs:389
#6  std::sys::pal::unix::stack_overflow::imp::install_main_guard_linux () at library/std/src/sys/pal/unix/stack_overflow.rs:442
#7  std::sys::pal::unix::stack_overflow::imp::install_main_guard () at library/std/src/sys/pal/unix/stack_overflow.rs:413
#8  std::sys::pal::unix::stack_overflow::imp::init () at library/std/src/sys/pal/unix/stack_overflow.rs:155
#9  std::sys::pal::unix::init () at library/std/src/sys/pal/unix/mod.rs:41
#10 std::rt::init () at library/std/src/rt.rs:118
#11 std::rt::lang_start_internal::{closure#0} () at library/std/src/rt.rs:173
#12 std::panicking::catch_unwind::do_call<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panicking.rs:581
#13 std::panicking::catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> () at library/std/src/panicking.rs:544
#14 std::panic::catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panic.rs:359
#15 std::rt::lang_start_internal () at library/std/src/rt.rs:171
#16 0x0000555555567957 in std::rt::lang_start<std::process::ExitCode> (main=0x555555567a20 <main::main>, argc=1, argv=0x7fffffffe5c8, sigpipe=0)
    at /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205
#17 0x0000555555567a5e in main ()
#18 0x00007ffff7d981ca in __libc_start_call_main (main=main@entry=0x555555567a40 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe5c8) at ../sysdeps/nptl/libc_start_call_main.h:58
#19 0x00007ffff7d9828b in __libc_start_main_impl (main=0x555555567a40 <main>, argc=1, argv=0x7fffffffe5c8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe5b8) at ../csu/libc-start.c:360
#20 0x0000555555567855 in _start ()

Breakpoint 1, __GI___libc_malloc (bytes=4) at ./malloc/malloc.c:3294
3294    in ./malloc/malloc.c
#0  __GI___libc_malloc (bytes=4) at ./malloc/malloc.c:3294
#1  0x000055555558f1e8 in alloc::alloc::alloc () at library/alloc/src/alloc.rs:95
#2  alloc::alloc::Global::alloc_impl_runtime () at library/alloc/src/alloc.rs:190
#3  alloc::alloc::Global::alloc_impl () at library/alloc/src/alloc.rs:312
#4  alloc::alloc::{impl#1}::allocate () at library/alloc/src/alloc.rs:429
#5  alloc::boxed::{impl#3}::try_clone_from_ref_in<str, alloc::alloc::Global> () at library/alloc/src/boxed.rs:872
#6  alloc::boxed::{impl#3}::clone_from_ref_in<str, alloc::alloc::Global> () at library/alloc/src/boxed.rs:831
#7  alloc::boxed::{impl#2}::clone_from_ref<str> () at library/alloc/src/boxed.rs:784
#8  alloc::boxed::convert::{impl#5}::from () at library/alloc/src/boxed/convert.rs:138
#9  core::ops::function::FnOnce::call_once<fn(&str) -> alloc::boxed::Box<str, alloc::alloc::Global>, (&str)> () at library/core/src/ops/function.rs:250
#10 core::option::Option::map<&str, alloc::boxed::Box<str, alloc::alloc::Global>, fn(&str) -> alloc::boxed::Box<str, alloc::alloc::Global>> () at library/core/src/option.rs:1165
#11 std::sys::pal::unix::stack_overflow::thread_info::set_current_info::{closure#0} () at library/std/src/sys/pal/unix/stack_overflow/thread_info.rs:114
#12 std::thread::current::with_current_name::{closure#0}<std::sys::pal::unix::stack_overflow::thread_info::set_current_info::{closure_env#0}, core::option::Option<alloc::boxed::Box<str, alloc::alloc::Global>>> () at library/std/src/thread/current.rs:234
#13 std::thread::current::try_with_current<std::thread::current::with_current_name::{closure_env#0}<std::sys::pal::unix::stack_overflow::thread_info::set_current_info::{closure_env#0}, core::option::Option<alloc::boxed::Box<str, alloc::alloc::Global>>>, core::option::Option<alloc::boxed::Box<str, alloc::alloc::Global>>> () at library/std/src/thread/current.rs:204
#14 std::thread::current::with_current_name<std::sys::pal::unix::stack_overflow::thread_info::set_current_info::{closure_env#0}, core::option::Option<alloc::boxed::Box<str, alloc::alloc::Global>>>
    () at library/std/src/thread/current.rs:216
#15 std::sys::pal::unix::stack_overflow::thread_info::set_current_info () at library/std/src/sys/pal/unix/stack_overflow/thread_info.rs:114
#16 0x000055555558cd3d in std::sys::pal::unix::stack_overflow::imp::init () at library/std/src/sys/pal/unix/stack_overflow.rs:179
#17 std::sys::pal::unix::init () at library/std/src/sys/pal/unix/mod.rs:41
#18 std::rt::init () at library/std/src/rt.rs:118
#19 std::rt::lang_start_internal::{closure#0} () at library/std/src/rt.rs:173
#20 std::panicking::catch_unwind::do_call<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panicking.rs:581
#21 std::panicking::catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> () at library/std/src/panicking.rs:544
#22 std::panic::catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panic.rs:359
#23 std::rt::lang_start_internal () at library/std/src/rt.rs:171
#24 0x0000555555567957 in std::rt::lang_start<std::process::ExitCode> (main=0x555555567a20 <main::main>, argc=1, argv=0x7fffffffe5c8, sigpipe=0)
    at /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205
#25 0x0000555555567a5e in main ()
#26 0x00007ffff7d981ca in __libc_start_call_main (main=main@entry=0x555555567a40 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe5c8) at ../sysdeps/nptl/libc_start_call_main.h:58
#27 0x00007ffff7d9828b in __libc_start_main_impl (main=0x555555567a40 <main>, argc=1, argv=0x7fffffffe5c8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe5b8) at ../csu/libc-start.c:360
#28 0x0000555555567855 in _start ()

Breakpoint 1, __GI___libc_malloc (bytes=544) at ./malloc/malloc.c:3294
3294    in ./malloc/malloc.c
#0  __GI___libc_malloc (bytes=544) at ./malloc/malloc.c:3294
#1  0x000055555558f3ea in alloc::alloc::alloc () at library/alloc/src/alloc.rs:95
#2  alloc::alloc::Global::alloc_impl_runtime () at library/alloc/src/alloc.rs:190
#3  alloc::alloc::Global::alloc_impl () at library/alloc/src/alloc.rs:312
#4  alloc::alloc::{impl#1}::allocate () at library/alloc/src/alloc.rs:429
#5  alloc::boxed::{impl#1}::try_new_uninit_in<alloc::collections::btree::node::LeafNode<usize, std::sys::pal::unix::stack_overflow::thread_info::ThreadInfo>, alloc::alloc::Global> ()
    at library/alloc/src/boxed.rs:614
#6  alloc::boxed::{impl#1}::new_uninit_in<alloc::collections::btree::node::LeafNode<usize, std::sys::pal::unix::stack_overflow::thread_info::ThreadInfo>, alloc::alloc::Global> ()
    at library/alloc/src/boxed.rs:581
#7  alloc::collections::btree::node::LeafNode::new<usize, std::sys::pal::unix::stack_overflow::thread_info::ThreadInfo, alloc::alloc::Global> () at library/alloc/src/collections/btree/node.rs:87
#8  alloc::collections::btree::node::NodeRef::new_leaf<usize, std::sys::pal::unix::stack_overflow::thread_info::ThreadInfo, alloc::alloc::Global> ()
    at library/alloc/src/collections/btree/node.rs:225
#9  alloc::collections::btree::map::entry::VacantEntry::insert_entry<usize, std::sys::pal::unix::stack_overflow::thread_info::ThreadInfo, alloc::alloc::Global> ()
    at library/alloc/src/collections/btree/map/entry.rs:403
#10 alloc::collections::btree::map::entry::VacantEntry::insert<usize, std::sys::pal::unix::stack_overflow::thread_info::ThreadInfo, alloc::alloc::Global> ()
    at library/alloc/src/collections/btree/map/entry.rs:377
#11 alloc::collections::btree::map::BTreeMap::insert<usize, std::sys::pal::unix::stack_overflow::thread_info::ThreadInfo, alloc::alloc::Global> () at library/alloc/src/collections/btree/map.rs:1053
#12 std::sys::pal::unix::stack_overflow::thread_info::set_current_info () at library/std/src/sys/pal/unix/stack_overflow/thread_info.rs:122
#13 0x000055555558cd3d in std::sys::pal::unix::stack_overflow::imp::init () at library/std/src/sys/pal/unix/stack_overflow.rs:179
#14 std::sys::pal::unix::init () at library/std/src/sys/pal/unix/mod.rs:41
#15 std::rt::init () at library/std/src/rt.rs:118
#16 std::rt::lang_start_internal::{closure#0} () at library/std/src/rt.rs:173
#17 std::panicking::catch_unwind::do_call<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panicking.rs:581
#18 std::panicking::catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> () at library/std/src/panicking.rs:544
#19 std::panic::catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> () at library/std/src/panic.rs:359
#20 std::rt::lang_start_internal () at library/std/src/rt.rs:171
#21 0x0000555555567957 in std::rt::lang_start<std::process::ExitCode> (main=0x555555567a20 <main::main>, argc=1, argv=0x7fffffffe5c8, sigpipe=0)
    at /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205
#22 0x0000555555567a5e in main ()
#23 0x00007ffff7d981ca in __libc_start_call_main (main=main@entry=0x555555567a40 <main>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe5c8) at ../sysdeps/nptl/libc_start_call_main.h:58
#24 0x00007ffff7d9828b in __libc_start_main_impl (main=0x555555567a40 <main>, argc=1, argv=0x7fffffffe5c8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe5b8) at ../csu/libc-start.c:360
#25 0x0000555555567855 in _start ()

(The backtraces are more pleasant to look at in your own terminal where they are formated with colors.)

On my Linux system, all malloc() backtraces have std::sys::pal::unix::stack_overflow::imp::init () in them.

Somewhat on a tangent..

I used to use ASan/LSan, in particular in crates that use highly manual memory management. Notably, ASan binaries would end with no reported memory leaks (unless I had a bug, or specifically created one to make sure ASan was working).

One day, after a pretty major upgrade (macOS, XCode and rust -- all at the same time), ASan would always report 160 memory leaks. IIRC, it looked like they happened before my main(). Even a crate with just an empty main() and nothing else would yield those 160 memory leaks.

It made the use of ASan super annoying. I found something hinting that it was possible to filter certain memory leaks -- I put that on my "investigate further" queue, but never got around to it.

One thing I've always wondered.. I don't think the number of leaks actually went from zero to 160 all of a sudden. My assumption is that ASan has implicit filtering for known-runtime-leaks-that-you-won't-be-able-to-do-anythig-about-anyway, and what happened was that some recent changes caused some/all of those filters to not work any longer.

Does anyone happen to know if this is the case? (Just randomly curious. I had mostly forgot about all of this, but this thread reminded me of it).

LSan, I don't know, but there are some "unfreed but reachable" allocations which Valgrind surpresses by default (but not as reliably as desired as per that issue). It wouldn't surprise me if LSan did something analogous.