It's really easy, but it's `unsafe`:
let my_bytes = [...];
let my_struct = unsafe {
std::mem::transmute(my_bytes);
};
It's necessarily unsafe, because Rust has no way of knowing what invariants the struct is responsible for upholding. If the struct contains a Vec, for example, then transmuting it from bytes will probably give you garbage pointers and a security vulnerability.I think something very interesting has happened with `unsafe` in the Rust community as the language has grown. There are lots of things that are "easy with `unsafe`", but everyone seems to round that up to "hard". I think that's a Very Good Thing, because it means that safe code is powerful enough and convenient enough that not using it is seen as a big deal.
By the way, regarding doubly linked lists, standard practice when dealing with anything that doesn't fit nicely with Rust's ownership model is to either (a) write a nice high-level abstraction over it that uses unsafe code, or (b) give up and use array indices to circumvent the borrow checker, which has the added bonuses of having smaller "pointers" and much better locality.
I disagree on the "disguise" part.
Yet, I don't see why you consider it an opposite of being a system language.
Rust encourages you to write code that works with things like slices and references instead of copying, because the compiler won't let you use them in a way that is error prone.
Last time I read a blog post about this, they picked a situation that was perfectly safe and ordinary in C++, with straightforward function-local safety reasoning.
struct Foo {
private:
vector<uint8_t> buf;
public:
// points into buf
vector<pair<const uint8_t *, size_t>> slices;
};
Well you can't do that. Obviously there are workarounds, like to return an object of type Foo holding the buf, with an api like impl Foo { fn getSlices(&self) -> Vec<&[u8]> }. Internally the object holds a Vec<(usize, usize)> or something like that, and you have a bunch of translation logic around your API's. And extra work to allocate the return value. So that's one example of Rust getting in your way.Probably some others would be uses of Interval<Buf>, and some cases where functions return Buf in https://github.com/srh/nihdb . I wanted to use &[u8] to represent the bounds of intervals, and IIRC that generally involved passing intervals upwards into functions, but for some reason I can't remember it got annoying and I couldn't be bothered to do it.
It doesn't seem unsafe to have a struct field refer to another member of that struct, but maybe I'm missing something.
https://play.rust-lang.org/?version=stable&mode=debug&editio...
Here, we have a self-referential struct. If you run this, you may get different numbers than me, but
[src/main.rs:17] &f = Foo {
x: 5,
p: 0x00007ffcbbba41c0
}
Here, p points to x. It's all good. The address of f is [src/main.rs:19] &f as *const Foo = 0x00007ffcbbba41b8
We move f into oh_no. Its address changes: [src/main.rs:25] &f as *const Foo = 0x00007ffcbbba3f28
... but p does not: [src/main.rs:26] &f = Foo {
x: 5,
p: 0x00007ffcbbba41c0
}
Any access of p is now a use-after-free.Does that make sense?
So to make this work, references would need to be re-written when a move/copy occurs.
I can still see having an easy way to construct self-referential structs being a useful thing, even if the compiler prevents you from moving them. Maybe with a smart clone() method that can update references correctly.
However, I am a little confused about this specific example. I don't understand why oh_no() taking ownership causes f to be copied to a new location. Shouldn't it remain in the same place on the stack? I feel like I'm missing something.
Yes! C++ has a concept called "move constructors" that allows for this (this would be that "smart clone" you talk about later in the comment), but we made a decision to not include it. This introduces some nice properties, at the cost of disallowing self-referencing structs.
> I can still see having an easy way to construct self-referential structs being a useful thing, even if the compiler prevents you from moving them.
So, in some sense, this is what the new Pin stuff is about. It lets you say "from this point on, this thing isn't going to move again" and therefore be self-referential.
> I don't understand why oh_no() taking ownership causes f to be copied to a new location.
"Taking owernship" means "move". "move" means "a memcpy from the old to the new location." Rust isn't really special from any other sort of language with value semantics here, other than disallowing you to use the old value, since it was moved out from. Does that make sense?
I guess my confusion is that I while realized "move" could mean a memcpy, I didn't realize it always meant a memcpy.
In other words, I thought the compiler would be smart enough to realized the memcpy is unnecessary, even though ownership has been transferred as far as the type system/borrow checker is concerned.
As another example, if you have this situation:
let x = 45;
dbg!(x);
let y = x;
dbg!(y);
Is there really any reason to allocate separate memory for y, if x is in accessible after y is defined?Or is this just an implementation detail, and could possibly change in the future?
Also, thanks for being so responsive! I suppose this isn't really the best place to have this discussion ;)
[EDIT] I realize that's not a good example, because 45 will just be copied. But you get what I was trying to show.
(And yeah, Copy types are like that too; the only semantic difference between Copy and move is if you can use the old binding.)
Any time! And yeah, the rust forums are probably better but it’s no big deal :)
Regardless, no worries, I was just trying to add clarity around details, just in case.
It advertises itself as a systems level language for the new age, but to me it feels like a functional language in disguise. The borrow checker enforces an unchained functional style (You may have 1 unique mutable reference or may have many immutable references). This sounds bad if you're used to pointer slinging, but once you get used to the peculiarities I find it forces me to write better quality code.
The package management is the most refreshing feature. It's a pain to rewrite my personal libraries in each language I use, but I found that most of the things I needed were already available at higher quality than I would have done. (And using them is a single config line).
For a 'low level language' they make it pretty hard to do some 'low level' things. Example's I've found include:
* writing a doubley linked-list. Consensus is "don't use linked-lists. The stdlib has better tools
* Reading a file into a struct. They make it surprisingly difficult to say "hey, read these 10 bytes, its this struct". Consensus is to use more structured file formats like json or protobufs. There's libraries for reading those things.
Error handling is similar in style to Go, but it get rid of a lot of the boilerplate. The '?' operator effectively acts as if err return err.
The macro system is really nice. I don't write many macros, but it does mean I can use other people's powerful macros. My favorite are 'include_bytes!', 'lazy_static!' and 'dbg!'. The new procedural macros are pretty wacky, essentially allowing you to parse or rewrite the AST. A powerful example I've seen is static checks; This [0] example writes a compile time check to assert that structs do not contain a member named 'bees'.
Overall it's been a fun language to mess around with.
[0] - https://tinkering.xyz/introduction-to-proc-macros/