Casting to/from raw memory

Context: toy database stuff

Suppose we have:

pub struct T { ... }

Is this enough to make it safe to cast to/from raw memory addresses that are multiples of 4096 ?

If not, can you please provide a counter example of a T ?

In particular, the 'Copy' should rule out things like Rc, Vec, ... which I believe should eliminate constructor / drop related issues. I am wondering if there are other issues I am not considering.

Yes, you can simply write 4096usize as *const T and nothing bad will happen. This cast does not require unsafe, i.e. it must not be unsound according to Rust's safety guarantee.

I don't possess the knowledge about raw pointer casts to tell you whether any kind of cast between different raw pointers is invalid, i.e. the compiler will complain about it, but that should be easy to test out, since that would be a compile error, if anything.

However, I would guess, that's not everything you want to do, though? Creating a raw pointer from an arbitrary address is in and of itself useless. Please, be more specific about your question.

In particular, if you want to dereference a raw pointer, I'd definitely need to know more about the kind of data (example is fine) at the address, how you get the memory address, what T looks internally and last but not least, how you ensure the data at the address is actually what it's supposed to be.

1 Like

The contents of memory still need to be valid for the type. If there's a NonZeroUsize inside T, for example, it's not safe to read from a memory location that has a 0 in that position. Similarly, any C-style enums must have a value corresponding to one of the declared variants.

If you put the wrong data inside one of these types, all sorts of things can go wrong: An Option<T> might act like None when it should be Some(_), match expressions on the enum can execute arbitrary arms, or worse.

1 Like

Yeah, sorry for not being clear. I definitely want to dereference these pointers. Here is the situation: I am playing with a toy database I need to build things like MetaNode, BtreeNode, etc ... . I also need these Nodes to be stored in mmap-ed memory.

The Copy trait says that, if you have a valid value of type T, a bitwise copy of that memory region placed somewhere else will also be a valid value of type T as long as alignment is preserved, and any lifetime annotations on T haven't expired.

Note that 'static ends when the program does, so T can't include any references if you want to read it from another process, even 'static ones. Code like this could be a problem, because the "Hello, World!" literal might be loaded into different addresses in different processes:

struct T(&'static str);

fn main() {
    let t = T("Hello, World!");
    // ...

Sorry, could you explain more? I am not seeing why yet.

I am willing to drop refs entirely. If we drop refs, are there still other problems?

When the OS runs your program, one of the things it does is decide where in memory each of the file sections will appear, and fix up internal pointers to refer to the correct location. There's no guarantee that it will choose the same addresses every time, so sending to pointer to another process can result in reading arbitrary memory.

Also, the program loader isn't the only way to get 'static references: There's also Box::leak, which prevents an arbitrary heap allocation from being freed. The other process likely never had a copy of that data in the first place, so sending just the memory address isn't useful.

I think that's ok, but someone else may come along and correct me. The larger problem is that there's no trait bounds that will enforce this for you because unannotated types are allowed to contain 'static references. Either the interface needs to be declared unsafe, or you need a list of known-good types that are allowed as struct fields.

You could do this by defining your own unsafe trait which marks types that are OK to use, and a derive macro that produces correct implementations:

unsafe trait Savable: Copy {}

unsafe impl Savable for u32 {}
// etc. for primitive / std types

struct T(u32);

// Derive produces something like this,
// with every field type appearing in the where clause:
unsafe impl Savable for T where u32: Savable {}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.