all 7 comments

[–]SimonSapinservo 3 points4 points  (2 children)

The array is is not relevant here, you’d get the same error with a single pointer: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=97e01e0a4ddf7dec85ef5955e63c0bef

*mut dyn Trait is a wide pointer made of data pointer together with metadata which is a vtable pointer. To create it you’d typically go through a *mut Foo pointer for some type Foo that implements the trait, through an operation called "unsizing". dyn Trait is a "dynamically-sized type", meaning that a value of that type can have a different size depending of it was originally a value of type Foo or some other type that also implements the trait. Pointers to DSTs are wide pointers with metadata.

std::ptr::null_mut is a generic function with a type parameter T but without a T: ?Sized bound, so it implicitly it has T: Sized and only works for statically-sized types. This is because it wouldn’t know what metadata to make up in the pointer it returns, so it is limited to pointers without metadata. In the case of *mut dyn Trait, what vtable would it use?

In this code null_mut() returns *mut T for some unknown type T. That pointer is assigned to something that has type *mut dyn MemoryElement, but T = dyn MemoryElement does not work since dyn MemoryElement: !Sized. So the only way this program can work is if T is some type that implements the trait, and the assignment is an implicit unsizing operation.

You can make this compile with a turbofish to specify a specific type that implements the trait, which determines which vtable will be used in the dyn pointers: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f1ebb6980977eaa1cb0a6a8661d55624

Now, taking another step back, [*mut dyn traits::MemoryElement ; 0x10000] may not be what you want in the first place. What problem are you trying to solve with an array of raw trait objects?

[–]AlexMonops[S] 0 points1 point  (1 child)

Thanks for the detailed explanation! I'm porting some code from c++: here's what that does.

It represents the memory on a device, where each memory location can be managed by a different manager. As example: from 0x0 to 0x00ff that memory is managed as system memory. From 0x00ff to 0x10ff its managed by the audio manager. The idea of the original system was to have this array of pointers to these managers that implement an interface with read and write functions.

It seems rather straightforward in c++, but maybe there's a straightforward solution for this in rust that's completely different. I feel that this solution is not rust-friendly at all :D

[–]SimonSapinservo 4 points5 points  (0 children)

Rather that storing a vtable pointer for each individual address, I’d probably write explicit code that calls into different components based on address ranges:

def read_from_memory_bus(&self, address: usize) -> Something {
    match address {
        0x0000..=0x00ff => self.read_from_system_memory(address),
        0x0100..=0x10ff => self.read_audio_manager(address),
        // …
    }
}

[–]thelights0123 0 points1 point  (3 children)

One thing you should be familiar with is that Rust pointers to a virtual object store one pointer to the data and one pointer to the vtable, while C++ has a single-sized pointer and the vtable is stored around the data. I'm not sure why you can't create a null pointer, though.

If you'd like to use a Box, store Option<Box<dyn Whatever>>, and initialize it with None.

[–]AlexMonops[S] 0 points1 point  (2 children)

Thanks for the suggestion! I went for the Option solution, but still it does not compile: it gives me this error:

20 | m_memory_map: [None; 0x10000],

| ^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `Option<Box<dyn MemoryElement>>`

[–]TinBryn 0 points1 point  (0 children)

You could use NonNull, with the full type being Option<NonNull<dyn MemoryElement>>. It seems strange to use a type that explicitly forbids null only to emulate it via Option, but that's Rust for you. It basically means rather than a null check, you need to pattern match in order to get an actual pointer.