LWN: Comments on "DebugFS on Rust" https://lwn.net/Articles/1041095/ This is a special feed containing comments posted to the individual LWN article titled "DebugFS on Rust". en-us Thu, 13 Nov 2025 11:12:37 +0000 Thu, 13 Nov 2025 11:12:37 +0000 https://www.rssboard.org/rss-specification lwn@lwn.net Rust functions are a bit more complicated than described https://lwn.net/Articles/1043353/ https://lwn.net/Articles/1043353/ pbonzini <div class="FormattedComment"> For what it's worth, the same function type "rematerialization" is quite pervasive in QEMU's experimental Rust bindings.<br> <p> The Rust wrappers for QEMU callbacks take the target Rust function as a type parameter, and a utility trait is added to Fn that causes a compiler error if that function is not zero-sized (<a href="https://lists.nongnu.org/archive/html/qemu-rust/2024-12/msg00090.html">https://lists.nongnu.org/archive/html/qemu-rust/2024-12/m...</a>).<br> </div> Sun, 26 Oct 2025 14:31:23 +0000 Rust functions are a bit more complicated than described https://lwn.net/Articles/1043130/ https://lwn.net/Articles/1043130/ daroc <div class="FormattedComment"> Thank you; that's a helpful clarification. I knew about the distinction between function pointers and function types, but I had misremembered which part of the compiler used the type information to emit static calls. I'll add a correction.<br> </div> Thu, 23 Oct 2025 22:20:48 +0000 Rust functions are a bit more complicated than described https://lwn.net/Articles/1043104/ https://lwn.net/Articles/1043104/ NYKevin <div class="FormattedComment"> <span class="QuotedText">&gt; Maurer's solution relies on the fact that, in Rust, every function and closure has its own unique type at compile time. This is done because it makes it easier for LLVM to apply certain optimizations — a call through Rust function pointer can often be lowered to a direct jump or a jump through a dispatch table, instead of a call through an actual pointer. This makes Rust function types unique zero-sized types: there is no actual data associated with them, because the type is enough for the compiler to determine the address of the function.</span><br> <p> Just to clarify things a bit, Rust actually has three different families of types that are relevant here:<br> <p> * Function pointers (written as fn(...) -&gt; ..., note the lowercase "f") - These are roughly equivalent to C function pointers, with the proviso that the pointee must be a safe function, or else the type must be prefixed with unsafe. In practice, these are not commonly used in Rust, because most contexts where you need them are better served by trait objects (dyn Trait, the Rust equivalent of C++ virtual method dispatch).<br> * Function item types - The type of a bare function name. For example, if foo() is declared somewhere in scope, then the expression "foo" has a function item type. These are zero-sized types as the article describes, but to be clear, LLVM does not "optimize" them. Instead, rustc emits a direct call to the underlying function at each call site (resolved during monomorphization, if necessary), and LLVM never even knows that we did any indirection in the first place.<br> * Closure types - For closures that do not capture anything, these are largely equivalent to function item types (the closure is emitted as if it was a top-level function, and callsites are emitted as direct calls). For capturing closures, these are anonymous structs that hold all the captured values, the closure is modified to take this struct as a hidden argument, and the callsites are again emitted as direct calls.<br> <p> Function item types and closure types are unnameable within the Rust type system - that is, while you can write the name foo as the only value of the foo function item type, you cannot write the name of that type (e.g. in a function signature). To use these types, they must be bound to a generic parameter, and you must then indicate to the Rust compiler that said parameter is callable using the Fn traits (note capital "F"):<br> <p> * FnOnce(...) -&gt; ... is implemented for everything callable (all types mentioned above), but it only allows you to call it once (because the underlying type might be a closure that consumes a captured value, and can't be called again). This takes a receiver type of self, which would normally indicate that it must have a size known at compile time. But in a mad anomaly, there is a blanket Box&lt;T: FnOnce + ?Sized&gt; implementation, which somehow(???) moves an unsized object out of the Box and passes it through to the FnOnce trait as if this restriction did not exist.[1] So you can have an unsized FnOnce if it lives on the heap, at least (and I guess the standard library can just decide it doesn't want to follow the language's usual rules if they prove inconvenient).<br> * FnMut(...) -&gt; ... is implemented for everything that you can call more than once, but requires you to have &amp;mut to the callable (because it might be a closure that mutates one of its captured values). Unlike FnOnce, neither this trait nor Fn take a self receiver by value, so there's no issue with unsized types here.<br> * Fn(...) -&gt; ... is implemented for all callables that don't mutate or consume state, including all function item types, closures that don't mutate their captures, and (safe) function pointers, as well as (shared) references to all of the above.<br> <p> If the generic parameter is bound to a function item type or closure type, then in order for monomorphization to work, rustc must consider both the callsite and the callee's source code at the same time. That in turn implies that monomorphization has the opportunity to inline these calls, although I'm not sure whether rustc makes that decision by itself or somehow involves LLVM in the process. This can be done cross-crate, even when LTO is disabled, because (as mentioned) monomorphization has an overriding need to look at the source code anyway.<br> <p> (There are also async functions, but those are mostly just syntactic sugar for regular functions that happen to return impl Future, and then all the really complicated stuff happens elsewhere in the type system.)<br> <p> [1]: <a href="https://doc.rust-lang.org/src/alloc/boxed.rs.html#1967">https://doc.rust-lang.org/src/alloc/boxed.rs.html#1967</a><br> </div> Thu, 23 Oct 2025 21:17:44 +0000 So do they just leak on module unload? https://lwn.net/Articles/1043028/ https://lwn.net/Articles/1043028/ daroc <div class="FormattedComment"> As I understand it, they're tied to a specific directory name, not to a module. So any part of the kernel that asks to unload that directory will unload them. Therefore yes: a newer version of the same module can tear down old entries if it wants to.<br> <p> On the Rust side of things, the files can persist until the File object is dropped or the scoped directory handle is dropped, depending on which API you use. Unless the driver takes special action to ensure those things leak, they should normally be dropped on module unload. So Rust drivers, at least, don't need to worry about that.<br> </div> Thu, 23 Oct 2025 13:15:57 +0000 So do they just leak on module unload? https://lwn.net/Articles/1042963/ https://lwn.net/Articles/1042963/ taladar <div class="FormattedComment"> <span class="QuotedText">&gt; Finally, DebugFS directories have to be manually torn down; they aren't scoped to an individual kernel module.</span><br> <p> So what happens on module unload here? Do they just leak if the module doesn't implement cleanup? Can the same module clean them up if it is loaded again? Or a newer version of that module during development where the programmer initially forgets to implement cleanup?<br> <p> Honestly, this seems like the exact kind of sloppy design common in C that Rust ownership is supposed to make harder to do accidentally.<br> </div> Thu, 23 Oct 2025 07:26:26 +0000