Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Uninitialized memory: Unsafe Rust is too hard (pocoo.org)
106 points by drrlvn on Jan 30, 2022 | hide | past | favorite | 125 comments


Without any unsafe code this is simply:

    let role = Role {
        name: "basic",
        flag: 1,
        disabled: false,
    };
The language tries to prevent you from interacting with a `Role` object that's not fully initialized. `mem::zero()` could work, but then you'll have to turn the `&'static str` into an `Option<&'static str>` or a raw pointer, to indicate that it might be null. You could also add `#[derive(Default)]` to the struct, to automatically get a `Role::default()` function to create a `Role` with and then modify the fields afterwards, if you want to set the fields in separate statements for some reason:

    let mut role = Role::default();
    role.name = "basic";
    role.flag = 1;
    role.disabled = false;
And even with `MaybeUninit` you can initialize the whole struct (without `unsafe`!) with `MaybeUninit::write`. It's just that partially initializing something is hard to get right, which is the point of the article I guess. But I wonder how commonly you would really want that, as it easily leads to mistakes.


Exactly this. I'm not sure what the author's practical goal is with that code. He rejects #[repr(C)] a few times, so it's not FFI.

Yes, working with uninitialized memory is tedious. But that isn't something you ever have to do. If you're translating some C to Rust, write it using Rust idioms, instead of trying to preserve every call to malloc/free and every access to uninitialized memory.


The point is to make points in a simple environment.

Anything small enough to clearly make points about unsafe Rust is almost certainly small enough to be done in safe Rust, defeating the purpose.


If it’s too big for an example, it’s almost certainly too big for “trust me, I know this is safe”.


I don't see how you can make this claim. An example is meant to be understandable after a cursory introduction and communicate an idea to the reader. If it takes me a day to understand the "example", it's not useful as an example, but real problems often take that long to completely understand in any language.


Also, the C version should really look like this:

    const struct role r = {
        .name = "basic",
        .flag = 1,
        .disabled = false,
    };
(of course this doesn't give you uninitialized memory in case new items are added to the struct, but why would one ever want that?)


A much better way to do partial initialization is by splitting up the struct into multiple parts. This can be easily done in safe rust with Option, or MaybeUninit if you're really desperate for performance.


When dealing with unix syscalls, you actually sometimes need to pass structs that aren't fully initialized, or are initialized to zero with the exception of some fields. The quintessential example is the sigaction struct.


Another good example is Win32, in many cases only the length is initialized and the API does the rest, this allows them to change the ABI across versions without impacting the caller.


That should work perfectly with multiple structs. Define

    #[repr("C")]
    struct MyThing {
        length: usize,
        data: MaybeUninit<MyInner>
    }
Then initialize with sizeof(data) and MaybeUninit::uninitialized(). When the call is complete, assume_init() and access the fields of the result struct as normal.


I see, thanks.


> The language tries to prevent you from interacting with a `Role` object that's not fully initialized.

I remember that after reading the Rust book, one of the first things I tried to do was to load a struct from a file. Like pseudocode:

    struct MY_STRUCT my_struct;
    read(file, &my_struct, sizeof(my_struct));
2 lines of code.... It should be simple, right? RiGhT?! Well, the first stackoverflow answer involved unsafe and a bunch of other stuff I didn't understand. And also I thought that as a beginner I shouldn't start fiddling with unsafe right away (otherwise, what's the point? I'm trying to move away from C). Then I learned that structs are not laid out as declared (OMG!), etc.

So, thinking this went beyond my skills I left it aside and tried to make a nice console logging library for my projects. It should be simple! I tried to create a variadic function and you can guess how it went.

I am out of luck with Rust.


>It should be simple, right?

Well, no. If you want to have memory safe subset, you absolutely cannot initialize structs with random bag of bytes in general case. C let's you cut corners here, but in Rust you need to implement (de)serializing logic (no need for unsafe).

>Then I learned that structs are not laid out as declared (OMG!), etc. >It should be simple! I tried to create a variadic function and you can guess how it went.

This is only surprising if you have this weird assumption that things should work like they do in C + some extra.


> in Rust you need to implement (de)serializing logic

Hmm. Well that sucks. It might be slower than reading into a struct directly (does more IO calls, there is more code to execute, probably uses more memory). My focus is on embedded applications, so "the less, the better".

> no need for unsafe

Can you point out an example on reading a bunch of structs from a file? Without std? Now I'm really curious.

> you have this weird assumption

You are probably right. I intended to use Rust in embedded as I said before so I thought a system language like Rust could fit. But you are right and the more I read (docs and comments like yours) I realize it's not C, and Rust may require extra steps and resources to achieve the same things I can do in C. With consequential hit on performance/code size/whatever, that it's a thing to consider seriously in constrained environments.


Rust works great in embedded. You do have to learn some things about how to get binary sizes down, depending on how resource constrained you are. If you're on a device that's running Linux, you probably don't need to think about it at all. If you're on a device and writing 100% of the code yourself, you may have to watch out for certain things, like formatting code, that can add a bunch of bloat, and choose alternatives.

At work we have a de novo microkernel we're using for the firmware of a few things inside of our product. A recent build we did to check on binary size of OS + 5 simple tasks using 22kb of flash and 3.5 kb of RAM. Those tasks are all separately compiled programs, it all gets put together on one single image to flash.

If you're talking 8 bit micros, you run into platform support issues before you even get to the binary size stuff, but if you're on Arm, even the low end, size is not the primary issue when it comes to doing Rust projects.


>Can you point out an example on reading a bunch of structs from a file? Without std? Now I'm really curious.

Not really, but here is really simple example:

  use std::fs::File;
  use std::io::Read;
  
  #[derive(Debug)]
  struct Point {
      x: i32,
      y: i32,
  }
  
  fn bytes2point(buf: [u8; 8]) -> Point {
      Point {
          // Slices to arrays is a bit unwieldy...
          x: i32::from_le_bytes(buf[..4].try_into().unwrap()),
          y: i32::from_le_bytes(buf[4..].try_into().unwrap()),
      }
  }
  
  fn main() {
      let mut buf: [u8; 8] = [0; 8];
      let mut file: File = File::open("data.txt").unwrap();
      Read::read(&mut file, &mut buf).unwrap();
      let point: Point = bytes2point(buf);
      println!("{:?}", point);
  }
It doesn't do any extra i/o compared to C. Std is only used for filesystem access and printing. It does require extra buffer.


Thank you.

I see. I'm looking now at how to load DOOM WAD files in Rust (https://github.com/bjt0/rs_wad/blob/master/src/wad.rs#L105) as an example, and I see it uses your method (even if it seems to store each piece in individual variables to then copy them into the struct).

It's not as straightforward as one would expect (as you say, it requires an extra buffer and parsing, but no extra IO). I surely wouldn't get to the solution by myself.


I'd be surprised if you see that Rust code is significantly slower than equivalent C code; this isn't how it pans out in most benchmarks. The killer for Rust is executable binary size, which even with no-std and other tricks can still balloon to an enormous size.


Here's another perspective on why things are the way they are:

One of the central philosophies of Rust is that it should not be possible to execute undefined behavior using only safe code. Rust's underlying core semantics end up being very similar to C's semantics, at least in terms of where undefined behavior can arise, and we can imagine Rust's references as being wrappers around the underlying pointer type that have extra requirements to ensure that they can be safely dereferenced in safe code without ever causing UB.

So consider a simple pointer dereference in C (*p)... how could that cause UB? Well, the obvious ones are that the pointer could be out-of-bounds or pointing to an expired memory location. So references (& and &mut) most point to a live memory location, even in unsafe code. Also pretty obviously, the pointer would be UB were it unaligned, so a Rust reference must be properly aligned.

Another one that should be familiar from the C context is that the memory location must be initialized. So the & reference in Rust means that the memory location must also be initialized... and since &mut implies &, so must &mut. This part is probably genuinely surprising, since it's a rule that doesn't apply to C.

The most surprising rule that applies here as well is that the memory location cannot be a trap representation (to use C's terminology). Yes--C has the same requirement here, but most people probably don't come across a platform that has trap representations in C. The reason why std::mem::uninitialized was deprecated in favor of MaybeUninit was that Rust has a type all of whose representations are trap representation (that's the ! type).

In short, the author is discovering two related issues here. First, the design of Rust is to push all of the burden of undefined behavior into unsafe code blocks, and the downside of that is that most programmers probably aren't sufficiently cognizant of UB rules to do that rule. Rust also pushes the UB of pointers to reference construction, whereas C makes most of its UB happen only on pointer dereference (constructing unaligned pointers being the exception).

The second issue is that Rust's syntax is geared to making safe Rust ergonomic, not unsafe Rust. This means that using the "usual" syntax rules in unsafe Rust blocks is more often than not UB, even when you're trying to avoid the inherent UB construction patterns. Struct projection (given a pointer/reference to a struct, get a pointer/reference to a field) is especially implicated here.

These combine when you deal with uninitialized memory references. This is a reasonably common pattern, but designing an always-safe abstraction for uninitialized memory is challenging. And Rust did screw this up, and the stability guidelines means the bad implementations are baked in for good (see, e.g., std::io::Read).


If Rust's syntax is geared to making safe Rust ergonomic, they should start by not requiring method calls to read or write from a &Cell<T> (because in C++ you don't need method calls to read or write from a T&).


Needing to use a `Cell` in rust is incredibly rare in my experience. It's very far from clear to me that the increased complexity coming from allowing overriding the meaning of `=`, and worse of `variable_name_that_happens_to_contain_a_certain_type` would be remotely worth it.


Unless GUI code comes up with Rc<RefCell<>> all over the place.


RefCell is distinct from Cell, more common, but much worse tradeoffs for implicit access, because accessing it can crash your program.


I want Cell to be more ergonomic because it addresses the same Rust weak points as RefCell but without runtime overhead and panicking. I think RefCell<struct> should be an infrequently used type that you reach for when you specifically want to guard against reentrancy, and struct{Cell} should be the primary replacement for shared access in other languages. The latter is sound but introduces a lot of boilerplate syntax.


> So we use a &'static str here instead of a C string so there are some changes to the C code.

> [..]

> So why does this type not support zero initialization? What do we have to change? Can zeroed not be used at all? Some of you might think that the answer is #[repr(C)] on the struct to force a C layout but that won't solve the problem.

The type of the first field was switched to a type (&str) that specifically promises it is never null. If the original type (a pointer) was kept, or a Option<&str> was used, mem::zero would've worked fine.


To the OP - why should creating uninitialized references with static lifetimes be easy? That is a recipe for undefined behavior - borrows aren't pointers, if you want a pointer to be zero initialized, then use a pointer.

If you want safe access to that pointer then wrap it in a struct with an accessor method


I think that the premise here is correct - writing unsafe Rust is too hard. There are lots of footguns.

This isn't a very good motivating example but I suppose it does the job of showing the various hoops one has to jump through when using unsafe.

I think right now the approach is to make unsafe "safe" (ie std::mem::uninitialized -> MaybeUninit) at the cost of complex, and eventually to build out improved helpers and abstractions. Obviously this is still ongoing.

But also, just don't write unsafe? It's very easy to avoid.


> But also, just don't write unsafe? It's very easy to avoid.

Yeah, there's a weird subset of developers who insist on mixing unsafe and safe code even when they're presented equally performant, safe alternatives. One such example was the Actix framework, where the lead dev refused to merge any fixes for his unsafe code. Eventually, so many merge requests showed up to fix his broken code that he just gave up the project altogether and let the community take over.

If you want to write unsafe code, I think that's perfectly fine, but Rust is not going to cater to your desires. C and C++ will give you the tools you need with the conveniences you want.


> If you want to write unsafe code, I think that's perfectly fine, but Rust is not going to cater to your desires. C and C++ will give you the tools you need with the conveniences you want.

This is a bit of a odd suggestion to me, honestly, you're basically saying Rust is not intended to be a C/C++ replacement. There is definitely a reality that not everything can be written in completely safe Rust, and a lot of the places that Rust could be the most beneficial (Ex. Linux Kernel) are going to require using it.


Calling Rust "a C/C++ replacement" is, I think, slightly underselling what it's supposed to do. As I understand it, the goal of Rust is more "take all these things that were historically hard to do safely and make them easier to do safely" than "replace C/C++ 1 for 1".


Is it actually achieving that goal though if the advice is "use C if you have to do `unsafe` stuff"? That's really my point. IMO the language was always intended to be able to be used in any situation C is used (with maybe a few exceptions), which includes situations where `unsafe` is necessary.


For starters, the example I gave (Actix framework) had no such situations where unsafe code was imperative to having it function. That's where the drama originated from there.

Secondly, I don't think the goal of Rust is to rewrite the entire kernel (or replace C/C++ for that matter). Rust is optimized around writing ergonomic, safe code. It's compiler is designed to produce high-performance binaries with minimal UB. Sure, they could designate a team for making the unsafe Rust experience better, but why bother? C++ already does that and does it well. The idea of "oxidation", or slowly replacing safety-critical portions of a program in Rust, doesn't necessitate fully replacing any of the languages that came before it, and while there are situations where unsafe code is undeniably necessary, I don't really think there's much of a reason to replace C or C++ for those uses. Any of the replacements you could write in Rust wouldn't be ergonomic, easy to read or well optimized.


I feel like you're ignoring the actual history here, these are a few excerpts from right before the 1.0 release of Rust[0]:

> Rust has remained true to its goal of providing the safety and convenience of modern programming languages, while still offering the efficiency and low-level control that C and C++ offer.

> When used in its own most minimal configuration, it is even possible to write an operating systems kernel in Rust.

> The resulting design is not only simpler to learn, but it is also much "closer to the metal" than we ever thought possible before. All Rust language constructs have a very direct mapping to machine operations, and Rust has no required runtime or external dependencies.

They do actually have a group working on making `unsafe` better, so they do somewhat recognize the problem (I'm not sure about the results, but the last time I looked at it was probably over a year ago): https://github.com/rust-lang/unsafe-code-guidelines

Perhaps another catch is that, at least in my experience, the most common situation where `unsafe` can into play is interop with other languages/libraries/etc.. Some of it is avoidable, but a certain amount of it is necessary just be able to interface with existing non-Rust code, so it's not really optional in those situations.

Also, just to be clear, I do actually like Rust, and I've written a few different things in it. I just think the state of `unsafe` is poor to the point that it's very hard to determine if you're doing something correctly, which in my opinion makes unsafe unusable for its purpose.

[0]: https://blog.rust-lang.org/2014/09/15/Rust-1.0.html

Edit: To be clear, I'm not attempting to defend Actix, that was silly. I'm just talking about situations where unsafe is unavoidable, which really covers a lot of potential usages of Rust.


When I want to write unsafe Rust code, I usually find it easier just to use C. The whole purpose for Rust in my work is to be safe.


The scary thing is:

Handling uninitialized memory is hard in C++ (and C), too.

You just don't notice and accidentally do it slightly wrong (mainly in C++, in C it's harder to mess up).


Exactly this. And also avoiding initializing memory with zeroes or something is often premature optimization. Very few programs are performant enough to notice the difference.


In my experience the most common use case for zero-memset is not optimizations but to reduce fallout if you happen to initialize a field..., like a newly added field.


In C, when you declare 'struct role r' (not as a static variable), it is not zeroed. The immediate Rust equivalent would be to use std::mem::uninitialized(), not std::mem::zeroed.


But an idiom from C which might inspire some unsafe rust is to memset a struct to zero after declaration in order to guarantee that all fields are initialized before anything would access them.


If I understand correctly, reading [0], in C99 (or later) you can do that with

    struct MyStruct foo = {};
This has the effect of initializing all members to zero (or, more precisely, the value which is the same as for objects that have static storage duration [0]).

[0] https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html

See also Stop Memsetting Structures: https://news.ycombinator.com/item?id=19766930


You need to put something in those braces. An empty initializer list is not allowed by C99 or C11. That being said, you only need to put ONE thing in there, the rest will then be initialized as you described.


Thanks, StackOverflow confirms you're correct. [0]

That's what I get for using GCC as an approximation of the C standard. (By default, GCC permits empty initializer lists.)

[0] https://stackoverflow.com/a/17589839/


Is initializing a NonZero field to 0 really initializing it?


no not at all

IMHO requiring all types in C to have a valid "all zero" variant so that this pattern can be used isn't grate either, somewhat of an anti-pattern even. But an anti-pattern needed for ergonomic C.

The post is a good example for trying to program "like in a different language" just because some tools somewhat allow it. Like in this case "programming rust like it's C".

And if you do rust FFI and have a lot of C experience it is tempting.


Sort of. It's definitely better than not initializing it.


I don't know rust, but why isn't the answer, don't try to do what you'd do in C like construct uninitialized structs?


They are pretty useful for a number of data structures, and Rust uses them heavily in the standard library.

I'm not aware of as many uses of field by field initialization of a struct but there is an example similar to this blog in the docs[1] (without the alignment considerations.)

That said my read has been the complexity is accidental as a result of language decisions to improve safe rust. MaybeUnit was only defined 3 years ago when it was discovered that mem::uninitialized /zeroed resulted in undefined behavior when used with bools. [2][3]

[1] https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#ini...

[2] https://github.com/rust-lang/rust/issues/53491

[3] https://doc.rust-lang.org/std/mem/fn.uninitialized.html


It's the correct answer.

You still sometimes need it like:

- when highly optimizing some algorithms

- doing FFI

So places you find it include some aync runtimes, some algorithm libraries, the standard library.

Still often times you initialize it by fully writing it, not by writing fields.

Anyway rules are simple:

1. use `ptr::write` instead of `ptr =`

2. use `addr_of_mut!(ptr.x)` instead of `&ptr.x` to get field pointers

3. uhm, `packed` structs are a mess, if you have some you need to take a lot of additional care, this is not limited to rust but also true for C/C++

Also you do not need `#[repr(C)]`, while the rust-specification is pending and as such `repr(Rust)` is pretty much undefined you still can expect fields to be aligned (as else you would have unaligned-`&` which is quite a problem and would likely cause a bunch of breakage through the eco-system).


There may of course be rare cases where having it uninitialized helps, but I would wager that even than the compiler could optimize it more often than not.


It's not that simple.

Like the unused bu allocated space in a Vec is basically a `[MaybeUninit<T>]`.

Or in async runtimes you often have an unsized type with an future trait object inlined (through unsized types anyway need a bit more love ;=) ).

Or some C FFI patterns.

But yes I would say in pure rust the use cases for `MaybeUninit` are rare, and the cases where you need pointers to fields even rarer.

Though while rare in comparison to the amount of code not needing it, still needed enough to be somewhere used (e.g. in a dependency) in many projects even if ignoring std.


Fair enough, I was thinking more along something like making a struct and then iteratively filling out its fields, maybe even through passing a reference to it to another function. After inlining, it should be “uninitalized” in x86 instructions.


Interoperating with C libraries can often force you into this sort of thing.


The write_unaligned is pure FUD. Regular unpacked structs don't violate alignment on fields!


In the spirit of being charitable, I would say that the article is highlighting a place where the guarantees are incompletely documented. And not having a solid specification and clear documentation is absolutely one of the things that makes unsafe Rust hard.

I'd actually like to qualify that a bit. Doing unsafe is not especially hard, just use pointers everywhere instead of references. That's exactly like C (which doesn't even have references), but the syntax is clunkier, you have to use function calls instead of concise operators like * and ->. What is hard is finely interleaving safe and unsafe Rust, as the author is trying to do. That's difficult because the unsafe code has to upload all the safety invariants of safe Rust, and those are indeed complicated.


> In the spirit of being charitable

You are being nice, but even if there is a documentation error, we can prove that if safe rust isn't completely broken, and `& x.field` is allowed in safe Rust, then fields must be aligned. It is just preposterous Rust would be more broken than C in this regard.

> but the syntax is clunkier, you have to use function calls instead of concise operators like * and ->.

Yes I agree, the syntax does suck. I see the macros use an unstable &raw, that would be more concise.

I think would be really good is if x->y in Rust matched &x->y in C. That is nicely orthogonal to dereferencing, and always safe.


There could be some loophole. If the program globally never makes a reference to a field, must it be aligned? If the field is referenced, could it be that when making a reference, the field gets copied or moved out and a reference is implemented as a pointer to that copy?


> If the program globally never makes a reference to a field, must it be aligned?

That would be whole-progam analysis which isn't done.

> If the field is referenced, could it be that when making a reference, the field gets copied or moved out and a reference is implemented as a pointer to that copy?

This breaks down with mutation, especially atomics, which are allowed as struct fields.


> That would be whole-progam analysis which isn't done.

That doesn’t matter.

> This breaks down with mutation, especially atomics, which are allowed as struct fields.

Not true at all. First, the compiler could refrain from doing this sort of code generation on fields with atomics (or any kind of mutable cell). Second, the object could be misaligned in some local contexts sometimes but references passed to functions could be always properly aligned. So a RefCell or atomic could still be copied or moved out and mutated, then moved back in, when the object is known not to have any references.

In general, this is close to reasonable code generation — a local struct could have some of its parts in registers. Its parts might be in noncontiguous stack locations. A language spec needs to specify that a field can be referenced or accessed via its pointer. It might also distinguish this from an alignment guarantee.


If the compiler is so omnicient, then you should just be able to take the pointer to the field in the unsafe struct too!

Surely such a genius compiler can see "oh, they took a field reference, better make sure that field is aligned" whether the reference is a safe one or not!


> That would be whole-progam analysis which isn't done.

Yet.


I agree, but IMO it's not all that clear to me that this is guaranteed. The documentation for the default layout pretty clearly says[0]:

> There are no guarantees of data layout made by this representation.

So that being the case, there's not really anything stopping them from introducing a situation where an unaligned field in a struct is created in the future. Of course I can't imagine why they would do that, but then maybe my imagination just isn't good enough. I think the author's point here (which is a good one in my opinion) is that when writing `unsafe` you're not supposed to rely on stuff that seems like it should be true, you're supposed to rely on stuff that's guaranteed to always be true, which with Rust isn't all that clearly defined.

[0]: https://doc.rust-lang.org/reference/type-layout.html#the-def...


If struct fields weren't aligned, then safe Rust would be completely and utterly broken. And really obviously so.

This isn't just a matter of things "seeming". It is quite literally an implication of Safe Rust works => field offsets must be aligned. There is no other way for safe Rust to be safe, other than alignment not mattering at all because all accesses are careful to pessimistically not rely on it.

I am sorry, but this hypothesis is just completely outside the Overton Window.


The thing is, C has an actual standard and people get bit making the same kinds of assumptions on things not stated. You may very well be right that there's no way this will ever happen, but then it also doesn't appear to be guaranteed. They literally say they make no guarantees about the data layout, but you're assuming there is a guarantee about the data layout :P

Maybe this time it works out, but there's plenty of other cases where things are not so clear cut.


What is the reason for the rule objects have to be always in a good state even inside unsafe?


One of the core ideas behind unsafe blocks is that they don’t actually change any semantics. All they do is allow more operations. This makes it a lot easier to reason about (for both the programmer and the compiler) since there’s not two different set of rules to remember.

It does however makes things a bit clunky since the unsafe bits need to ensure a “safe” state throughout the whole block and not only by the end of it.


I’ve never understood why they don’t just support partially initialized structs. They’re already doing control flow analysis for initializing variables. It seems like a natural extension to do this for aggregate types (product and sum). From a type theory perspective, a struct T is not a T in unitialized state, it’s. “partial T” that at some point gets transformed into a T. So, you’d not be able to use the T in the normal sense until the compiler can prove that all fields have been init on all possible control flow paths.

Why is this problematic? (I presume there is a fatal flaw as it seems too obvious of a solution..)


Rust already supports partially deinitialized structs - that is, moving out of a struct field by field - and there’s no fundamental reason it can’t support partial initialization too.

Indeed, there’s an open issue for it:

https://github.com/rust-lang/rust/issues/54987

But there are a lot of desired features with open issues, so don’t expect this to be implemented anytime soon unless someone takes an interest in it.


Ah okay this makes sense. I wish I personally had the spare time to contribute this.


It's not problematic, it's just not work that's been done yet. Rust isn't finished. I bet you could get an RFC pushed through and implement it if you wanted to, it certainly is a promising idea.


As an example, in C the following is quite common:

Data dat;

dat.a = 1;

dat.b = “foobar”;

do_thing(&dat);

I seems like the compiler should be able to treat “dat” as an “maybe uninit” type until after “dat.b” gets assigned.


Because the compiler makes assumptions on the valid layouts of a type, e.g. packing Option<bool> in a single byte. Prior to 2018 it was assumed the UB was only on read like C but that turned out not to be the case. To avoid this the type has to reflect that it might be uninitialized - hence the transparent MaybeUnit wrapper.


But the code isn't equivalent. The C code just has a pointer to a manually allocated buffer, while the Rust does the equivalent of zeroing out (or leaving uninitialized) a C++ std::string. Akin to:

auto name = reinterpret_cast<std::string >(malloc(sizeof(std::string))); memset(name, 0, sizeof(std::string); *name = "basic";

But on the stack.


> For instance `(*role).name` creates a `&mut &'static str` behind the scenes which is illegal, even if we can't observe it because the memory where it points to is not initialized.

Where is this coming from? It's literally not true. The MIR for this has:

        ((*_3).0: &str) = const "basic";
        ((*_3).2: u32) = const 1_u32;
        ((*_3).1: bool) = const false;
So it's only going to do a raw offset and then assign to it, which is identical to `*ptr::addr_of_mut!((*role).field) = value`.

Sadly there's no way to tell miri to consider `&mut T` valid only if `T` is valid (that choice is not settled yet, AFAIK, at the language design level), in order to demonstrate the difference (https://github.com/rust-lang/miri/issues/1638).

The other claim, "dereferencing is illegal", is more likely, but unlike popular misconception, "dereference" is a syntactic concept, that turns a (pointer/reference) "value" into a "place".

There's no "operation" of "dereference" to attach dynamic semantics to. After all, `ptr::addr_of_mut!(*p).write(x)` has to remain as valid as `p.write(x)`, and it does literally contain a "dereference" operation (and so do your field projections).

So it's still inaccurate. I believe what you want is to say that in `place = value` the destination `place` has to hold a valid value, as if we were doing `mem::replace(&mut place, value)`. This is indeed true for types that have destructors in them, since those would need to run (which in itself is why `write` on pointers exists - it long existed before any of the newer ideas about "indirect validity" in recent years).

However, you have `Copy` types there, and those are definitely not different from `<*mut T>::write` to assign to, today. I don't see us having to change that, but I'm also not seeing any references to where these ideas are coming from.

> I'm pretty sure we can depend on things being aligned

What do you mean "pretty sure"? Of course you can, otherwise it would be UB to allow safe references to those fields! Anything else would be unsound. In fact, this goes hand in hand with the main significant omission of this post: this is not how you're supposed to use `MaybeUninit`.

All of this raw pointer stuff is a distraction from the fact that what you want is `&mut MaybeUninit<FieldType>`. Then all of the things about reference validity are necessarily true, and you can safely initialize the value. The only `unsafe` operation in this entire blog post, that isn't unnecessarily added in, is `assume_init`.

What the author doesn't mention is that Rust fails to let you convert between `&mut MaybeUninit<Struct>` and some hypothetical `&mut StructBut<replace Field with MaybeUninit<Field>>` because the language isn't powerful enough to do it automatically. This was one of the saddest things about `MaybeUninit` (and we tried to rectify it for at least arrays).

This is where I was going to link to a custom derive that someone has written to generate that kind of transform manually (with the necessary check for safe field access wrt alignment). To my shock, I can't find one. Did I see one and did it have a funny name? (the one thing I did find was a macro crate but unlike a derive those have a harder time checking everything so I had to report https://github.com/youngspe/project-uninit/issues/1)


This author doesn't even seem to know C properly so it's hard to accept their reasoning.

This in Rust:

  let mut role: Role = mem::zeroed();
Is not the same as this in C:

  struct role r;
C does not zero initialize.


I am under the impression that even _safe_ Rust is really hard to learn. Several years ago I started with GoLang and it was so easy to start programming even advanced things almost instantly..Rust drives me crazy. The syntax seems overcomplicated, the compiler errors are cryptic, the IDE is not helpful.


I suspect you might start to receive a bunch of replies from people with the opposite experience (replies from people who find the syntax easy to read, and the compiler errors clear and understandable). I would like to attempt to preempt that by noting that it's totally reasonable for two people to feel drastically different things about a programming language. Neither view is more correct than the other.

That said, I am curious why different people have these different feelings. One aspect is likely rooted in the fact all of our brains are different. But I also wonder if first impressions play a big role here. A good example of a cryptic rust error is the `expected type Foo, but found type Foo` error message which is very inscrutable, especially to a new users. There are also some lifetime errors that can be hard to understand.

I wonder if someone encounters these type of messages very early on in their learning experiences, the unpleasantness of having to decipher them colors the rest of their learning experiences.


> That said, I am curious why different people have these different feelings. One aspect is likely rooted in the fact all of our brains are different. But I also wonder if first impressions play a big role here.

Path dependency has big impact on what seems natural, intuitive, etc. Part of that is what you've done before, and part is first impressions, and part of it is your approach to learning (or the approach taking to teaching you) the subject at hand.

I’ve approached Rust via different books and tutorials before and come up with the “it’s awesome, but too hard” feeling and set it aside.

Recently I've been trying Hands-on Rust [0] and going off to the side from it and Rust is clicking pretty well. Not sure if the book is a better fit for me, if the past false starts have prepared the ground, or what specifically changed.

[0] https://pragprog.com/titles/hwrust/hands-on-rust/


> Path dependency has big impact on what seems natural, intuitive, etc

So much this. One example I hit a lot is when people keep saying “async/await is too hard, green threads are much more intuitive”: coming from a JavaScript background I feel async/await and all the future combinators much more intuitive than dealing with threads and channels. Before async rust was stabilized I had to learn how to use threads and I was always frustrated how clunky it felt, and now that Rust has async/await I use it for everything IO related, because to me it's just much more familiar.


I hadn't heard of the term "Path dependence" before, thanks for sharing that. I think your comment is a better articulation of what I was trying to say: it's not actually about your first impressions with rust, but really about all your past experiences with programming and learning programming languages (up to and including your first impressions with rust)


Despite being contemporaries, both nominally for systems programming, Rust and Go are very different, starting with memory management. If you're used to writing (correct) C++ programs, particularly those that exploit move semantics in C++11 and later, then you can appreciate what Rust is doing for you, weird syntax and all.

On the other hand, if you're fine with a garbage collector, which most people are most of the time, then Go is going to feel more natural. For some people, Go is more comparable to Python than Rust, because of this one big difference.


The big issue I had with learning Rust is not the simple stuff -- I could write safe Rust fairly quickly. The problem I continue to have is that many internal behaviors of the language are totally undocumented and inexplicable. For example, implicit reborrows. They happen everywhere, but it's still just a pile of unicorn farts as far as documentation. The idea of a language with no documentation for a given syntax is horrifying. Rust does very well in documentation for beginners, but if you actually want to understand the language, if you want to know what's really happening, you're usually fucked.


> A good example of a cryptic rust error is the `expected type Foo, but found type Foo` error message which is very inscrutable, especially to a new users.

Does Rust actually give an error like `expected type Foo, but found type Foo`, as in both types are the same in the error? I don't think I've seen that before, but I don't write much Rust.

If both types are the same, what does the error mean?


I've seen this error a lot when working with two different versions of the same library. Specifically, you can directly include version A, but a different library depends on a version B, with some of that exposed in the public API.


I think the compiler will now give you a hint that they may be from different versions. If not, next time you see it you should open up a bug in rustc.

To anyone who gets a cryptic error message - that's a bug, report it.


Yup, newer compilers will give you an explicit hint about thee types from being different versions of different crates, but older compilers did not.


Why was this downvoted? Rust may be the "most loved" language, but after a few weeks with it, I don't love it. We've got to face the reality that it makes simple things way too complicated.

In fairness, I have found the compiler errors to be extremely helpful. They often tell me exactly what to fix. But honestly, they shouldn't have to do that. The syntax should have been obvious from the beginning, as it is in most programming languages.


What syntax "should have been obvious from the beginning"? I always felt like most things in Rust are around as simple as they could be, for the things the language is trying to do.


Stuff like

    type TestResult = Result<(), Box<dyn std::error::Error>>
is not obvious nor simple and makes a joke of the claim that Rust is simpler than C++ in error handling. Damn, I prefer straightforward C++ exceptions after seeing stuff like this.


I guess because it's off topic. TFA is about a specific Rust issue and a generic comment about "Rust is hard to learn" doesn't add anything to the conversation. It's like the generic "C++ is way to complicated" comment under every C++ post. They just incite language flame wars and district from the actually interesting stuff.


It's because, for some reason, the rust community is extremely, overly aggressive about downvoting. I think we can all just admit that - I've been using Rust since 2014 and it's always been an issue and it needs to be called out.


I haven't commented/been upvoted enough to be allowed to downvote, and it is honestly quite liberating and lets me focus on elevating what I think are positive contributions.


Right, the HN comments are all about being on topic.


Rust also makes complicated things really simple, which is why it's been the most loved for many years in a row.


Prior exposure to C appears to be negatively correlated with ease in learning Rust. This is why I say Rust is not a language for C programmers -- it's for their replacements. Young programmers with only a few years' experience in JavaScript or Ruby are now coding circles around the old C wizards, contributing bare-metal, bit-banging code that is guaranteed to be free of several classes of bugs those C wizards are still struggling with.


That's surprising. I would've thought most C programmers have at least some experience writing C++, and C++ programmers (and C programmers too, but I don't know anyone who programs primarily in C so I can't comment) already do many of the things Rust does for you. Like thinking about lifetimes, const by default, moving instead of copying, avoiding raw pointers like the plague, etc.

Someone who has never thought about that stuff must surely find it harder to appreciate Rust.


Rust initially was much harder to learn. The compiler was considerably more strict - rejecting a lot of programs that were entirely valid. That earned rust a very negative "hard language" reputation early on.

That hasn't been the case for years. The 2018 edition officially stabilized Non-Lexical Lifetimes, allowing tons of valid programs to work. There have been a lot of other improvements since then to address papercuts.

At this point Rust is a pretty easy language to learn imo.


Agree. I don't need to get 10 Phds figuring out Rust. At this point in my career distributed algos/systems are much more interesting to understand and implement. And writing those in a way that are bindable to multiple clients means staying close to C/Zig/C++.

Over the years the following C++ aspects have and continue to drive me nuts. On a scale of (Sarcasm coming next) 10:

* (7/10) build time, and dependency management with the 81 million tools, formats, approaches to deal with this.

* (1/10) 21 page error sets resulting from single word typos in templated code: even Egyptologists ask how do we put up with that? At least we have nice rocks to look at. Yah, they have rocks. How come we don't have rocks?

* (1/10) Code decl duplication between .h/.cpp

* (1/10) Long, pointless C++ errors. if you have an orthodox background the guilt trip C++ lays on for mismatched function calls is legion; you really feel it. It tells you such-n-such function could not be matched ... but look at the 42 million function calls you could have made ... should have made ... and it's killing me you didn't make; you seeing the effort I'm putting into this? It's killing me ... I just need the cop to say it straight: you screwed up. Ticket. See in court. Have a nice day. I prefer one liners.

I'm in no rush to waste time on Rust. I'd rather stick to GO where I can, Zig when I can. At the office we're 65% C++, 25% C. Stack overflows, memory corruptions do occur periodically; I've sorted several of those out. But the new code is C++ which makes heavy use of STL. Much better. There is growing adoption in GO and Rust, but the vast amount of C/C++ code means the apple will not fall too far from the tree.


I believe that different programming languages resonate better with some more than others. Often it's a matter of preferences, comfort, perceived usability, and previous experiences.

So, it's quite possible for various people to feel more comfortable using Golang than Rust, and the opposite can be true as well, where using Rust is preferred over Golang. At the end of the day, it will usually come down to individual or corporate priorities.

Another of the newer languages that fall into a similar usage space would be Vlang (https://github.com/vlang/v). It being debatably easier to learn and use, in the context of languages that more easily interact with C.


> I am under the impression that even _safe_ Rust is really hard to learn.

[...]

> The syntax seems overcomplicated, the compiler errors are cryptic, the IDE is not helpful.

Yes, Rust is hard to learn. Rust does _seem_ over-complicated.

However I like to compare Rust to exercise. You need to do a bit of it before you start reaping the benefits of it.

If you suspend your judgement for a bit and try to write some Rust, starting from the very beginning you will find that:

- Rust is actually a small language at its core, unlike the monstrosity that is C++ . You don't really need Advanced Rust to be productive. Use Advanced Rust only when you're... advanced

- Rust actually is very consistent

- The Rust compiler is actually very helpful. It's the least cryptic compiler I've met. But its OK if you feel that now as you're just beginning your journey with Rust

Avoid the temptation to "read" Rust from a book. Try to _do_ Rust. Otherwise it might overwhelm you. Simply keep adding Rust techniques to your arsenal as you mature in your usage of Rust.

Learning Rust changed the way I look at programing. Rust is a beautiful language. As a random example, just look at the the Firecracker VMM written in Rust -- https://github.com/firecracker-microvm/firecracker . It would have been able to very difficult for me to understand the codebase if it were written in C/C++!

Rust is one of those rare languages I've encountered that if the code compiles, there is a high probability it will work. The type system is that good!

TL;DR Persist and you will reap the rewards with Rust.


I much prefer Rust over C++, but I find that the problems of C++ have little to do with the language itself.

I've been watching the videos by Andreas King on SerenityOS and the code is so clean that at first I wondered what programming language I was even looking at. I see the SerenityOS codebase as proof that if C++ programmers wanted to write modern, elegant, readable code, they definitely could.

In practice, though, most C++ programs are full of legacy code or are written by people who don't necessarily know about or agree with modern ways to program C++. It's easy to write beautiful code if you also wrote the memory manager and standard library in modern C++, but most people don't have that luxury.

By being created with a more modern standard library, Rust has an advantage over C++. There is no legacy code to remain compatible with and there is no real way to write "old-fashioned" Rust because the project hasn't existed for long enough. I've seen plenty of terrible, ugly Rust, most of it in my own personal projects. The strictness of the language and standard toolset helps, but it's far from a guarantee that enterprise Rust will be readable and clear.


Having acquired C++ into my toolbox in 1993, and thus lived through its adoption over C (which still owns several domains after 50 years), I am bettting that Rust at 30 years of age into production will suffer similar fate.


Even though Rust's Editions don't solve everything they make a huge difference and they also change the nature of the conversation around such evolution.

I think the built-in array type is illustrative. In both languages (C++ and Rust) the initial 1.0 language offers a built in array type that is provided with built-in syntax and parsing but isn't as good as the user-made container types, so on day one the situation is OK, yeah, we do have arrays but you should likely avoid them.

In C++ that just remains the case, C++ 20 has poor built-in arrays and a note saying we built another array type that you should actually use, it's in our standard library.

Meanwhile in Rust they've been improving their built-in arrays, using const generics, implementing IntoIterator for arrays, and so on. Rust 2021 in a compiler today has pretty nice built-in arrays that behave how you'd expect for a container, a sophisticated programmer might notice that Default isn't implemented for your array of 64 integers, but such sharp corners are now few and far between and further refinements continue.

The resulting conversation is more open to change, even though Editions can't actually do magic they can conceal some pretty deep compiler magic like the hack to enable IntoIterator for arrays yet keep working Rust 2015 and Rust 2018 code that assumed into_iter() on an array will go via a reference. Being able to get to 90% of what people wanted with no magic meant the conversation about extra magic happened and it might otherwise not have.

Editions also spur language innovations that make further edition work easier. Rust 1.0 did not have any way to talk about an identifier if it collided with a keyword, which of course means if you reserve a new keyword now you can't access identifiers which used the now-reserved name. Rust 2018 introduces raw identifiers to fix that, if you really insist on naming your function "try" you can write r#try despite the existence of the keyword try.

I think these benefits are cumulative, and although Rust 2045 might have some cruft it will have a lot less than C++ 23 let alone C++ 44.


I don't buy into editions, for me I hardly see them any different from language version switches available across programming languages.

Mainly, because:

1 - They require the whole code, including all third party dependencies to be available to the compiler;

2 - There is the issue about possible inconsistencies across Rust compilers, when they start to be more widespread;

3 - They are relatively constrained the scope, e.g. semantic differences across versions and how that can be handled across crates public API

Let's see how editions take care about the many items that are yet to stabilise.

Naturally Rust being 30 years younger (approximately), it will always get less cruft.

Maybe by 2045 it can manage to do the same play C++ did on C, and there will be a LLVM replacement in Rust, while at the same time, there will be domains that regardless how much cruft C++ has gained, they will continue to use, just like it almost impossible to take C out of UNIX clones and embedded, no matter how much C++ has tried.


The requirement to have all the source if you're not happy with C-style ABI boundaries comes from Rust itself not from its Editions. C++ leaves a bunch of performance on the table here because it insists on pretending your code from last century should get to dictate how a CPU designed last year works.

Hyrum's law will indeed bite multiple Rust implementations, as it already bites Rust on non-x86 CPUs because a low level language cannot entirely conceal implementation details. This is already a huge leap over C++ where you get to tickle previously undetected Undefined Behaviour if you change compilers because (safe) Rust stays safe even if you're astonished that e.g. documentation saying "the order is not defined" really means "the order is not defined" despite the fact you tested it on your laptop last week and that's how it worked. Your bad program might have bugs but it doesn't suddenly exhibit nonsense behaviour.

Fundamentally Rust's Editions are about the fact that we don't get it right first time every time. In C++ having settled on the claim that (quoting Stroustrup) "this array concept is inherently low level" the built-in array is basically abandoned as unusable garbage forever because to do otherwise would admit a mistake. In contrast Rust says sure, arrays don't yet have all the features you expect of first class containers, we will improve the language to deliver that.

I think this is a healthier philosophy and it's already successful. It isn't a panacea, but it is a very noticeable improvement.

As to taking care of items that are yet to stabilise, what's to see here? Even for something like generators that is far from finished, the keyword is reserved already, you probably wouldn't wait for an edition to land it, once it was stable you just flip the toggle and it works.


(All responding to 1) A. Binary dependencies were a mistake. B. No, editions absolutely do not require all dependencies to be fully available in source. Editions are purely a front end thing and as long as you have enough info for the api/abi, you're fine. C. Headers are still source code. You cannot use any dependency without partial source, meaning we'd be stuck with that anyway.


C headers don't give away implementation.

If Rust doesn't want the enterprise and game development markets it is ok, there is enough space for all.


Enterprise is very much a trailing indicator. On the whole Enterprise will adopt Rust the same way it adopted Version Control and the Internet, years late and reluctantly. Nobody making those decisions knows the first thing about technology, but that cuts both ways. It means there aren't going to be executives at Mondelez International chasing their software engineers to use Rust because it's a good idea, but it also means despite lobbying by C++ consultants, those executives don't care that an internal team started using Rust for a new project.

As to Game Development, well, one of the things Rust has been trying to do is avoid toxic people and if you wanted a concentrated supply of toxic people the video game industry is where you'd look. I expect there will be a lot of Rust in or near video games in the next decade, but I already pity the people working on them.


Definitely. I think Rust's more restrictive nature will leave it a little better than C and C++, but as technology evolves, every language will eventually show its age.


>It would have been able to very difficult for me to understand the codebase if it were written in C/C++!

I find rust and modern C++ codebases equally hard to understand. How can you be so sure that the reason you find it easier to understand is not simply because you know rust better than C or C++?


>"Avoid the temptation to "read" Rust from a book. Try to _do_ Rust. Otherwise it might overwhelm you. Simply keep adding Rust techniques to your arsenal as you mature in your usage of Rust."

I used to be the opposite in the ancient times. Would read a book and then start programming. But then the books were relatively tiny. Now most languages has matured to the state of having an insane amount of features. And the result is definitely what you say. Just read some brief overview on basic language constructs and then proceed by learning on on-need basis as you progress.

Among the others I program in C++ for example but I would shoot myself if asked to read something resembling its complete description. Sorry I have a life to live. And I am using only subset of C++ that solves my particular needs. If I feel my code does not look nice when doing some particular stuff then the time comes to do some more reading.


That is inspiring. Thanks a lot!

Perhaps the problem was that I was overconfident and immediately started with pretty advanced Rust - writing a web assembly that performs big data analysis using the "polars" library.


I consider myself reasonably competent in Rust but the webassembly stuff always trips me up, and so do most cross-language tools. You might've just had an unfortunate experience. I'm not sure if I'd pick Rust for WASM unless you already know it, but I suppose it's a good reason to learn the language.

The problem with Rust is that to write good Rust, you need to accept that you can't use the same approach to solve a problem you could use in other languages. The borrow checker and its implications aren't necessarily difficult, but they're different.

I'd compare the experience to someone who started in Python learning Haskell for the first time: it can take weeks or more before one can truly understand a monad. If you start with basic functional programming you can be quite productive before you need such code structures, but when you open a code base where monads are mixed liberally, your head will be spinning.

Rust may be a lot closer to traditional imperative languages, but the implications of the safety mechanisms in the language are something you can't just skip over. It'll take time and practice to write code that the Rust compiler likes.

If you're getting started, I'd recommend writing some toy programs for the CLI instead. I also recommend using "clippy" to warn you of code smells (it'll suggest improvements when it can!) and to get links to specific problems you might not be aware of. As for the IDE, I recommend the rust-analyser plugin over the native "Rust" plugins found in most IDEs, because the basic ones fail to do proper macro expansion and leave you guessing on how to use libraries.


>> I'm not sure if I'd pick Rust for WASM

What would be an alternative if I plan to develop a high-performant data analysis tool for WASM(similar to Perspective[0])? I looked at the list of supported languages[1] and Rust seems to be a good choice.

[0] - https://github.com/finos/perspective

[1] - https://github.com/appcypher/awesome-wasm-langs


I can't say I'm too experienced in writing code for WASM, but I have to say I was pleasantly surprised when I experimented with Go.

It has to be said that I have many (mostly subjective) problems with the Go language and the ecosystem, but with the help of GoLand I was productive in minutes. The layer for exchanging arguments between the browser and the "native" code is a bit weird, but once you get passed that, it's easy to get going.

The Rust problem with WASM is more about learning Rust well and picking the right libraries (many of them have dependencies that don't work well in the browser!). Setting up tools like cargo to compile usable WASM files also takes a little practice, but that's at most an afternoon of messing around before you should be reasonably comfortable with it. In my opinion, the main improvements Rust brings to the table are the (memory) security features and the fearless multithreading, but neither of them are of much use within the WASM runtime. The borrow checker will still help you write correct code, but it can be an unnecessary pain in the ass when it doesn't need to be. Rust is a great systems programming language, but I'm not so sure about it becoming the de-facto WASM standard.

Of course, if you already know Rust, or know a library that would be super useful to you, it's great that Rust can Just Work (TM) with the right setup. First-party tooling support is pretty great for a language to have!

If I had to choose, I think I'd pick a language that I'm comfortable with (C#, Kotlin, Java) and has the necessary libraries easily available, and see if the tooling works well for my use cases.

I'm also watching Zig evolve with interest; it's not quite there yet, but it's integration with C libraries and some of its more modern language features are very promising. WASM code doesn't need many of the complexities modern languages bring, but older languages like C can lead to dangerous programming paradigms, so I think a mix between the two can produce clean, performant and fully-featured code. I wouldn't recommend it for production use yet, though, as the language is still in constant development with breaking changes between point releases!


To be honest, I would even consider C for WASM despite my usual rants, after all with the security sales pitch for Webassembly it shouldn't matter after all.


Agreed, without the risk of memory corruption and the associated security risks, C can be used without too much hesitation (as long as your complicated pointer code doesn't break your own data structures and mess up your program, of course!).

I think it would be a challenge to compile all dependencies for a fully features C program into WASM, but if you pick your libraries well, C could be an excellent language for speeding up complex calculations in Javascript.


> Because that raw pointer does not implement deref and because Rust has no -> operator we now need to dereference the pointer permanently to assign the fields with that awkward syntax.

Absolutely not, you can still use a mutable reference:

    let role = &mut *uninit.as_mut_ptr();
    role.name = "basic";


The article says

> A mutable reference must also never point to an invalid object, so doing let role = &mut *uninit.as_mut_ptr() if that object is not fully initialized is also wrong.

I'm curious who's right here, because I've seen your pattern in code recently!


The docs for as_mut_ptr: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.h...

> Incorrect usage of this method:

  let mut x = MaybeUninit::<Vec<u32>>::uninit();
  let x_vec = unsafe { &mut *x.as_mut_ptr() };
  // We have created a reference to an uninitialized vector! This is undefined behavior.
Also, above, it explicitly describes the intended API for partially initializing a struct: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.h...


Thanks Steve!

It turns out I had misremembered; the cast I was thinking of is

https://github.com/oxidecomputer/hubris/blob/master/sys/kern...

from &mut MaybeUninit<[T]> to &mut [MaybeUninit<T>], which doesn't construct a reference to something uninitialized.


Aren't all the `(*role)` in their code "mutable references" too?

    (*role).name = "basic";


No, this is specifically not creating a mutable reference. So it's fine except for the fact that writing via assignment like this calls the destructor/drop for the old value, if the type has a Drop implementation. That's why &str is fine but String is not, since it will call Drop on a zeroed String (Which is not guaranteed to work) or on an uninitialized String. Using ptr::write instead explicitly does not Drop the old value that is being overwritten.

Basically, if you have something like

    let v = vec![1,2,3];
    v = vec![4,5,6]; // Here vec![1,2,3] is dropped otherwise we would be leaking


Do you have a source/explanation for *role not being a mutable reference? What is this expression's type then?

You mention ptr::write() but it is not used here, and I don't see how you could use it to write to a field?


(*role) is not a reference at all, it is a value. its type is Role.


lvalue not mutable reference, got it. I'm surprised there are contexts where one is fine but the other is UB, but I understand the rule.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: