https://lwn.net/SubscriberLink/907876/ac0afc756df0ec71/ LWN.net Logo LWN .net News from the source LWN * Content + Weekly Edition + Archives + Search + Kernel + Security + Distributions + Events calendar + Unread comments + ------------------------------------------------------------- + LWN FAQ + Write for us User: [ ] Password: [ ] [Log in] | [Subscribe] | [Register] Subscribe / Log in / New account The perils of pinning [LWN subscriber-only content] Welcome to LWN.net The following subscription-only content has been made available to you by an LWN subscriber. Thousands of subscribers depend on LWN for the best news from the Linux and free software communities. If you enjoy this article, please consider subscribing to LWN. Thank you for visiting LWN.net! By Jonathan Corbet September 15, 2022 --------------------------------------------------------------------- Kangrejos Parts of the Rust language may look familiar to C programmers, but the two languages differ in fundamental ways. One difference that turns out to be problematic for kernel programming is the stability of data in memory -- or the lack thereof. A challenging session at the 2022 Kangrejos conference wrestled with ways to deal with objects that should not be moved behind the programmer's back. C programmers take full responsibility for the allocation of memory and the placement of data structures in that memory. Rust, instead, takes most of that work -- and the associated control -- out of the programmer's hands. There are a number of interesting behaviors that result from this control, one of which being that the Rust compiler will happily move objects in memory whenever that seems like the thing to do. Since the compiler knows where the references to an object are, it can move that object safely -- most of the time. Things can go badly, though, when dealing with self-referential data structures. Consider the humble (C) list_head structure that is heavily used in the kernel: struct list_head { struct list_head *next, *prev; }; It is possible to create a Rust wrapper around a type like this, but there are complications. As an example, initializing a list_head structure to indicate an empty list is done by setting both the next and prev fields to point to the structure itself. If, after that happens, the compiler decides to move the structure, those pointers will now point to the wrong place; the resulting disorder does not demonstrate the sort of memory safety that Rust hopes to provide. The same thing can happen with non-empty lists, of course, and with a number of other kernel data structures. Benno Lossin started his Kangrejos talk by saying that Rust provides a mechanism to deal with this problem, which can also arise in pure Rust code. That mechanism is the Pin wrapper type; placing an object of some other type into a Pin will nail down its location in memory so that it can no longer be moved. There are numerous complications, including the need to use unsafe code to access fields within a pinned structure and to implement "pin projection" to make those fields available generally. [Benno Lossin] The really big challenge, though, is in a surprising area: initialization. Fully understanding the issues involves require a level of Rust guru status far beyond anything your editor could hope to attain, but it seems to come down to a couple of aspects of how Rust treats objects. Rust goes out of its way to ensure that, if an object exists, it has been properly initialized. Object initialization in Rust tends to happen on the stack, but objects that need to live indefinitely will need to move to the heap before being pinned. That movement will break a self-referential object, but pinning before initialization will break Rust's memory-safety rules. Solutions exist, but require a lot of unsafe code; Lossin has been working on alternatives. He initially tried to use const generics to track initialization, but the solution required the use of procedural macros and was complex overall. And, at the end, it was "unsound", a Rust-community term indicating that it was not able to properly handle all cases. So that approach was abandoned. Instead, he has come up with a solution that uses (or abuses) struct initialization and macros. Your editor will not attempt a full description of how it works; the whole thing can be seen in Lossin's slides. Among other things, it requires using some complex macros that implement a not-Rust-like syntax, making the code look foreign even to those who are accustomed to Rust. The response in the room was that, while this work is clearly a clever hack, it looks like a workaround for a limitation of the Rust language. It's the kind of thing that can create resistance within the kernel community, many members of which already find Rust hard to read (though it should be said that kernel developers are entirely willing to merge C preprocessor hackery when it gets the job done). There was a strong desire to see a different solution. Xuan "Gary" Guo stepped up to show an alternative approach. In C, he began, it is easy to create an object without initializing it, or to initialize an object twice. In Rust, anything that circumvents the normal initialization routine requires unsafe code. Tracking the initialization of such objects can require maintaining an initial variable to hold the current state, which would be a good thing to avoid. Other approaches can require additional memory allocations, which are also not good for the kernel. [Xuan Guo] There have been attempts to address the problem with, for example, the pin_init crate. But pin_init still is unable to initialize self-referential structures, and has to do its own parsing of Rust structures and expressions. That requires the syn crate, which is not really suitable for kernel building. A proper solution for the kernel, he said, would have a number of characteristics. It should be safe, impose no extra cost, and require no additional memory allocations. Aggregation should work; a structure containing multiple pinned objects should initialize properly. The mechanism used should not look much different from normal Rust. There should also be no assumptions about whether initialization can fail or not. Guo's solution (which can be seen in his slides), looks a bit closer to normal Rust than Lossin's, but it still depends on complex macro trickery and has not managed to avoid using the syn crate. And it still can't handle self-referential structures properly. But it is arguably a step in the right direction. Once again, though, the proposed solution looked like an impressive hack, but the response was not entirely favorable. Kent Overstreet described it as "really gross", adding that this job should be done by the compiler. Wedson Almeida Filho responded that the compiler developers would just suggest using procedural macros instead. One compiler developer, Josh Triplett, happened to be in the room; he said that there could be help provided by the language, but that requires an RFC describing the desired behavior, and nobody has written it yet. The session wound down without any specific conclusions other than, perhaps, a desire to pursue a better solution within the Rust language rather than trying to work around it. Index entries for this article Kernel Development tools/Rust Conference Kangrejos/2022 [Send a free link] ----------------------------------------- (Log in to post comments) The perils of pinning Posted Sep 15, 2022 14:38 UTC (Thu) by mb (subscriber, #50428) [Link] >That requires the syn crate, which is not really suitable for kernel building. Why? >that the compiler developers would just suggest using procedural macros instead. What's wrong with that? Why not use proc macros? [Reply to this comment] The perils of pinning Posted Sep 16, 2022 2:35 UTC (Fri) by milesrout (subscriber, #126894) [Link] >> That requires the syn crate, which is not really suitable for kernel building. >Why? There are a few reasons a crate might not be suitable for kernel building. The most important that I can think of would be if the crate requires the standard library. Rust for Linux, as far as I am aware, requires a freestanding (no-std) environment. Looking at the pin-init crate, it requires 'syn' as a runtime dependency (not just a compile-time ("dev") dependency)[0]. [0]: https://github.com/nbdd0121/pin-init/blob/trunk/pin-init-... > What's wrong with that? Why not use proc macros? As the article notes: >> it requires using some complex macros that implement a not-Rust-like syntax, making the code look foreign even to those who are accustomed to Rust. and later >> it still depends on complex macro trickery and has not managed to avoid using the syn crate. And it still can't handle self-referential structures properly. So, complexity for one thing. Creating additional sublanguages isn't really ideal. Would you need to write code that uses these procedural macros in every structure that includes a list head? In every self-referential structure? In every structure that includes a self-referential structure? In every structure that includes those structures? Etc? Procedural macros are also known for slowing down compilation times if used too much. If there is a procedural macro invocation in the definition of every structure that can be part of a linked list, that might significantly slow down compilation times compared to a solution that did not require procedural macros. Rust compile times are already pretty slow, even without procedural macros, compared to C, so making them even worse would not be ideal. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 6:54 UTC (Fri) by mb (subscriber, #50428) [Link] >The most important that I can think of would be if the crate requires the standard library. Rust for Linux, as far as I am aware, requires a freestanding (no-std) environment. But not for building. The part of the article and my question was about building. >Would you need to write code that uses these procedural macros in every structure that includes a list head? In every self-referential structure? In every structure that includes a self-referential structure? In every structure that includes those structures? Etc? I don't see why this would be a blocker. Especially for a thing that could eventually be migrated over to a solution provided by the rust compiler or core. And the whole "sublanguage" thing is the whole point of macros. Also in C. Look at what the kernel does with C macros. That's a completely separate language. Have you looked at the slides for the proposed proc macros? The only non-idiomatic thing is that a dot (.) appears in an unusual place. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 12:27 UTC (Fri) by milesrout (subscriber, # 126894) [Link] >But not for building. >The part of the article and my question was about building. pin-init does not require the syn crate just for building. As I said, and as demonstrated in my link, it requires it as a dependency (run-time dependency), not as a dev dependency (Rust lingo for a build-time dependency). >I don't see why this would be a blocker. Especially for a thing that could eventually be migrated over to a solution provided by the rust compiler or core. As noted in the article: "Once again, though, the proposed solution looked like an impressive hack, but the response was not entirely favorable. Kent Overstreet described it as "really gross", adding that this job should be done by the compiler. Wedson Almeida Filho responded that the compiler developers would just suggest using procedural macros instead. One compiler developer, Josh Triplett, happened to be in the room; he said that there could be help provided by the language, but that requires an RFC describing the desired behavior, and nobody has written it yet." So there is absolutely no guarantee that it would just be a thing that would "eventually" be migrated over - as with most stopgap measures in the world of software, it would probably end up being permanent. > And the whole "sublanguage" thing is the whole point of macros. The supposed point of using Rust for the Linux kernel as opposed to one of the other millions of memory-safe languages out there (recall that almost all programming languages are memory-safe) is that it is well-suited to interfacing with C and writing low-level code using the "unsafe" escape hatch while allowing those uses to be wrapped in type-safe memory-safe wrappers. If, to accomplish the most basic low-level task (of working with these sorts of structures), it requires the use of procedural macros, it strongly suggests that the language isn't actually ready. After all, if the point of Rust is that Rust is well-suited to this, then by definition *Rust* is well-suited to it, not Rust-with-various-extensions. It's one thing to write macros to reduce boilerplate, but this is writing macros to cover over a gaping hole in the most basic functionality of the language, at least if the language claims to be designed for efficient interoperability with C code. This isn't meant to be a criticism of Rust in general. I'm just explaining why people would balk at the idea of needing to use procedural macros for something so basic - needing to extend the language to deal with this means people will expect that it will need to be extended for lots more things in the future, yknow? Then you aren't really using Rust, you're using Rust++, so why use Rust in the first place? > Also in C. Look at what the kernel does with C macros. That's a completely separate language. What the kernel does with C macros is certainly not a "completely separate language" - in fact, what the kernel does with macros is done in such a way as to make their use mostly invisible. The vast majority of the time you do not need to know, when you write foo(x), whether foo is a macro or a function. That is why the Linux kernel uses lower case macro names: they're designed to blend in, not to stick out like a sore thumb as ALL_UPPER_CASE macros do. C macros are also not procedural. They involve simple text substitution. Rust has macro_rules or whatever for that kind of relatively simple macro. This is different: it is significantly more complicated and produces significantly slower compilation times. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 14:27 UTC (Fri) by Bigos (subscriber, #96807) [ Link] pin-init (or more specifically - pin-init-internal crate you have linked) is a proc_macro crate. A runtime dependency of a proc_macro crate is effectively a build-time dependency of the proc_macro users. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 14:57 UTC (Thu) by 0x3333 (subscriber, #158599) [ Link] Am I the only one who thinks that Rust is great but unsuitable for Kernel development? Man, I feel that a lot of workarounds have to be made to allow this. What a pity! [Reply to this comment] The perils of pinning Posted Sep 15, 2022 15:05 UTC (Thu) by mb (subscriber, #50428) [Link] >Am I the only one who thinks that Rust is great but unsuitable for Kernel development I don't know, if you're the only one. But Kernels and lots of bare metal code have already been developed in Rust. Rust in Linux is merely fighting with the existing C interfaces and C concepts. These have never been developed with Rust in mind and they cannot be changed just to support Rust. I also don't think that these problems make it unsuitable. It just means that we have to live with a couple more unsafe statements for now. In C every statement is unsafe. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 15:12 UTC (Thu) by 0x3333 (subscriber, #158599) [ Link] >I don't know, if you're the only one. But Kernels and lots of bare metal code have already been developed in Rust. Well, I was referring to Linux Kernel, I used rust for embedded microcontrollers, but for Linux Kernel it looks(from outside) that is too much to fight. Anyways, it's good to see safety landing on the kernel. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 15:13 UTC (Fri) by calumapplepie (subscriber, # 143655) [Link] Kernel code is not standards-compliant C code. If you look at a random file in the kernel, it isn't recognizable as C code. The number of macros, specialized functions (no malloc), compiler extensions, inline assembly (usually via a macro, but it's there), goto's, and memory barriers even in a bog-standard driver is substantial. If you look at more performance-focused subsystems, you start seeing self-modifying code, overlaying structs on top of each other, and intentionally created and carefully controlled data races. Expecting Rust to interface with all that without any nasty hacks is a little unfair. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 15:01 UTC (Thu) by GhePeU (subscriber, #56133) [ Link] Asahi Lina, who's writing a Rust kernel driver for the Apple M1/M2 GPU, had some problems when initializing structures that seem related: https://twitter.com/LinaAsahi/status/1567752082060619776 https://twitter.com/LinaAsahi/status/1570119306461204481 [Reply to this comment] The perils of pinning Posted Sep 15, 2022 16:15 UTC (Thu) by excors (subscriber, #95769) [ Link] I've seen a similar issue when trying to initialise a large statically-allocated object, on a microcontroller with no heap and a small stack. Idiomatic Rust will construct a temporary value then move it into the static object (or heap etc), and usually the compiler will optimise away the copy so it constructs in-place; but beyond a certain size threshold (e.g. a few hundred array elements) it won't do that optimisation, it will construct the temporary on the stack then memcpy it into the destination. That's unacceptable with large objects and small stacks, and I'm not sure of a good way to solve it. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 3:14 UTC (Fri) by developer122 (subscriber, # 152928) [Link] TBH this really sounds like a compiler bug specifically, and the overall problem needs to be fixed in the language itself. There's doesn't really seem to be a good reason you can't do what people want to do, just that the language itself doesn't support pinning and init at the same time. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 15:16 UTC (Thu) by mathstuf (subscriber, #69389) [Link] > And, at the end, it was "unsound", a Rust-community term indicating that it was not able to properly handle all cases. So that approach was abandoned. My understanding is that "unsound" comes from type theory and is basically the equivalent of "if this is true, we can prove 1=2". Rust might extend that to also involve "you can break invariants without using `unsafe` with this API", but, AFAIK, the term itself is not a "Rust community" term. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 21:03 UTC (Thu) by riking (subscriber, #95706) [ Link] More specifically, it's "you can trigger undefined behavior without using 'unsafe' with this API". The undefined behavior can then be leveraged to later prove that 1 = 2. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 22:09 UTC (Thu) by khim (subscriber, #9252) [Link ] > AFAIK, the term itself is not a "Rust community" term. You are 100% correct. It's not "Rust community" term. It's Rust reference term: It is the programmer's responsibility when writing unsafe code to ensure that any safe code interacting with the unsafe code cannot trigger these behaviors. unsafe code that satisfies this property for any safe client is called sound; if unsafe code can be misused by safe code to exhibit undefined behavior, it is unsound . [Reply to this comment] The perils of pinning Posted Sep 16, 2022 16:00 UTC (Fri) by epa (subscriber, #39769) [Link ] If it doesn't handle all cases, then I would call it incomplete. To be unsound would mean it can produce incorrect results. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 15:22 UTC (Thu) by Bigos (subscriber, #96807) [ Link] > That mechanism is the Pin wrapper type; placing an object of some other type into a Pin will nail down its location in memory so that it can no longer be moved. You do not place an object in Pin. Instead, you place a "pointer type" (a shared/exclusive reference, Box, etc.) inside it. From what I recall, the contract is that if you create a Pin

(where P is a pointer, like &mut T) you promise you will never move the pointed-to object (unless it implements Unpin, which is a trait automatically implemented unless opted-out; self-referential types must not implement it, though). If object initialization (like setting self-referent pointer to itself for list_head) requires the object to be Pinned, that indeed is problematic. The common case is to initialize the object without any pinning and when it is placed where it should only Pin it there. This is how Futures work (the first Future::poll() call pins the future, so it can be moved and combined before that happens). Could MaybeUninit help represent the uninitialized, not-pinned object state? It indeed requires the use of unsafe, but safe wrappers could Pin the object, initialize it and return a Pin<&mut T>. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 16:42 UTC (Thu) by atnot (subscriber, #124910) [ Link] > You do not place an object in Pin. Instead, you place a "pointer type" (a shared/exclusive reference, Box, etc.) inside it. Indeed this is why I think describing pinning in terms of the Rust compiler implicitly moving things around is confusing and unhelpful: C moves things around implicitly just as much as Rust. If you take a struct in C and pass it somewhere by value, that struct is now in a new location. If you do that with a self referential struct, it is now invalid in the new location. This applies identically to both languages. However, safe Rust would not let you do that, because self reference requires borrowing, and borrowed values can not be moved/passed by value. So where is the problem then? Well, Rust also allows users to *explicitly* do things like use std::mem::swap to swap the contents of two mutable references. This means that if you can get an exclusive reference to something, you can move it. That is why pointer types to location aware things are a problem and need to be wrapped in another type that prevents access, like Pin. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 19:33 UTC (Thu) by fw (subscriber, #26023) [Link] If you take a struct in C and pass it somewhere by value, that struct is now in a new location. If you do that with a self referential struct, it is now invalid in the new location. C only has copies, not moves, so the result is completely valid. It's just that the new object is not self-referential anymore: its pointers refer to the old object instead. But these pointers remain valid while the old object is still around. That's completely different with Rust move semantics. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 20:23 UTC (Thu) by mb (subscriber, #50428) [Link] >That's completely different with Rust move semantics. No. Not really. The only difference is that in Rust the "the old object is still around" is zero time. Everything else is the same. Rust can't rip out memory from your machine. Therefore it also technically copies. The difference between a copy and a move is that the lifetime of the old object is terminated immediately and not at some later point. And based on that the compiler might be able to apply some optimizations to omit the copy altogether. And if you copy a self referential object, it's by definition not self referential after copy. It references another object (the original one!). Not self. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 1:59 UTC (Fri) by JoeBuck (subscriber, #2330) [ Link] Unless the copy operation can somehow update the self-references to reference the copy, so that a potentially circular structure can be cloned. But that could be expensive. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 18:10 UTC (Fri) by tialaramex (subscriber, # 21167) [Link] Rust deliberately doesn't have constructors, including copy constructors. If the compiler moves your Cheese, it will do so using (something morally equivalent to) memcpy(). It won't, and you can't, tweak anything, the exact bits are moved, if those bits refer to their location in memory that is probably bad since now they don't. Rust's Clone trait, which is what happens if you clone() something that admits it can be cloned, is a function you can implement. For example my Multiplicity https://docs.rs/misfortunate/1.0.0/ misfortunate/struct.Mu... wrapper type claims to be Clone, despite the fact all it asks of your wrapped type T is that it should be Default. Multiplicity's clone() implementation just makes a Default::default() of your type, which is type correct but presumably not what you wanted. (The name is a reference to the 1996 Andie MacDowell comedy in which Michael Keaton clones himself repeatedly and gradually the clones deteriorate in quality) But you deliberately can't intervene in the simple memcpy() behaviour when the compiler decides to move your object in memory. The Rust compiler reserves the rights to do this more often than you expect, or less often, and in different places, as it sees fit. Pinning stops it from doing that, at some potential cost in performance. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 23:06 UTC (Thu) by atnot (subscriber, #124910) [ Link] You are right about it being valid as far as the C spec is concerned, in the same sense that -1 is a valid file descriptor. However you've still violated the assumptions the rest of the code makes about that type, which is likely to be much less happy about that fact :) Of course, this is a completely moot point in C because it's always possible to get any memory into an arbitrary state anyway. But Rust doesn't have the luxury of being able to tell people just not to do things. Hence unlike C, it does not allow you to access the old object after a copy (unless it's marked with the special Copy trait). Which is after all the only difference between a copy and a move, it's not like the old memory disappears. [Reply to this comment] The perils of pinning Posted Sep 16, 2022 2:15 UTC (Fri) by wahern (subscriber, #37304) [ Link] > But Rust doesn't have the luxury of being able to tell people just not to do things. Rather, Rust has the luxury of telling people they *can't* do things. Which is critical to and in some cases necessary for Rust being able to provide its safety guarantees. Complex, memory-aware data structures (e.g. cyclical, self-referential, etc) have *always* been a sore point for Rust. Most Rust developers come from the world of scripting languages or similar environments (e.g. Java) where hash tables are the primary, even sometimes exclusive primitive (beyond arrays) for building higher-order data structures, so they don't feel any loss. For this group, more complex data structures always come by way of magic libraries with generic interfaces that you glue together; building thin, bespoke data structures for your specific functional problems as a matter of course is a foreign concept. Many solutions Linux has adopted for higher performance, lower latency, multi-core scalability, etc simply wouldn't have even been considered at all when writing everything in scratch from Rust. It's probably the case that equally (or at least sufficiently) performant Rust-consonant alternatives exist for most individual cases. The real question is whether at scale, when you're stitching all these solutions together into what is essentially a single, complex data structure, you can end up in the same place in terms of performance; or if the solutions Rust demands intrinsically impose greater costs when composed together. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 15:38 UTC (Thu) by atnot (subscriber, #124910) [ Link] > And, at the end, it was "unsound", a Rust-community term indicating that it was not able to properly handle all cases. So that approach was abandoned. To clarify, unsoundness means that an abstraction allows you to subvert the safety guarantees of Rust, violating some contract between the programmer and the compiler. For example, by allowing you to obtain an invalid pointer under some usually contrived circumstance. This is different from being unsafe, where an interface has requirements that can not be automatically proven, and a bug or vulnerability, where the interface is still sound but the code is not implemented correctly. Hence the jargon. > The session wound down without any specific conclusions other than, perhaps, a desire to pursue a better solution within the Rust language rather than trying to work around it. I'm personally excited about what the kernel can bring here. The reason Rust doesn't really have a nice solution to pinning is that in almost every case, self referential types can be replaced with indexes at no loss (or even a gain) to clarity or performance. In the few exceptions that existed, adding another allocation or some unsafe code was not a big deal. I expect that will still be the case most of the time within the kernel, but integrating with a codebase that makes very heavy use of self reference has great potential to increase the motivation to fix some of the papercuts and warts in this area. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 21:45 UTC (Thu) by gray_-_wolf (subscriber, # 131074) [Link] Disclaimer: I never wrote a single line of rust and my knowledge of the language is "I've read few blog posts" level. My knowledge of kernel source code is... superficial at best. With that said, I have two questions about this: > As an example, initializing a list_head structure to indicate an empty list is done by setting both the next and prev fields to point to the structure itself. 1) Why thought? Isn't the idiomatic C way setting the next and prev to 0? Assuming that is not possible in rust (to prevent null dereference I assume), why not use rust-version of std::optional? 2) How does this work together with C code? Are the next and prev converted between &self and 0 every time they are passed over the boundary? Or does kernel use &self as well instead of 0? [Reply to this comment] The perils of pinning Posted Sep 15, 2022 22:36 UTC (Thu) by Sesse (subscriber, #53779) [ Link] It depends on whether your list is supposed to be circular or not, one would suppose, and whether you have explicit sentinel objects or use NULL to mark end. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 22:43 UTC (Thu) by khim (subscriber, #9252) [Link ] It's double-linked list in Linus's good taste. The idea is that there are never any NULL pointers and thus never need to deal with them. And, of course, when you convert that data structure to Rust you want to keep that property... and you couldn't! Rust doesn't have any constructors and structures are created in one place (on stack) and then are moved into proper position. This was never a problem for new Rust code, but it's very often quite awkward. And indeed, language-level solution would be great. But before it can be implemented it first needs to be imagined and it's not that easy here. [Reply to this comment] The perils of pinning Posted Sep 15, 2022 22:36 UTC (Thu) by khim (subscriber, #9252) [Link ] > 1) Why thought? Isn't the idiomatic C way setting the next and prev to 0? Assuming that is not possible in rust (to prevent null dereference I assume), why not use rust-version of std::optional? Both are possible. You couldn't use references in these self-referential data structures because mutable references are, but definition, unique. And pointers are unsafe but nullable. Using