A macro that serializes and returns a pointer

I found this interesting problem. I first implemented this as a trait, then I started getting undefined behaviour. Then I noticed that it might be because I was serializing, then getting the pointer to a vector that does not exist anymore. I had the idea then of doing a macro, because then it's text substitution:

#[macro_export]
macro_rules! as_buffer {
	($e:expr) => {
		{
			let t = || -> Result<(*const u8, usize), AsBufferError> {
				let mut buffer: Vec<u8> = Vec::new();
				$e.serialize(&mut buffer)?;
				let buffer_pointer = buffer.as_slice().as_ptr();
				Ok((buffer_pointer, buffer.len()))
			};
			t()
		}
	};
}
pub use as_buffer;

but I accidentally created a new scope, for which the vector also vanishes.

Is there a way to do this stuff in a macro such that the vector continues alive so I can use the pointer right after the let (pointer, len) = as_buffer(my_struct).unwrap()?

What you are trying to do smells like UB to me.

The let buffer_pointer = buffer.as_slice().as_ptr() bit means you'll return a reference to the data managed by buffer, but buffer is automatically dropped when the closure returns. This is a textbook use-after-free and if you were using references instead of pointers the compiler would have told you what is wrong.

The correct solution is to hoist buffer into a higher scope and make sure no pointers to it outlive the buffer variable.

Why do you want a pointer to a serialized version of your struct in the first place?

yes, I noticed that, but I don't know how to do it in a macro, because I need to put the thing on the macro inside {} which creates a new scope. I tried:

#[macro_export]
macro_rules! as_buffer {
	($e:expr) => {
		{
			let mut buffer: Vec<u8> = Vec::new();
			$e.serialize(&mut buffer).unwrap();//temporary unwrap, dont know what to do here. The closure was so I could get either Ok or Err, but anyways
			let buffer_pointer = buffer.as_slice().as_ptr();
			Result::<(*const u8, usize), AsBufferError>::Ok((buffer_pointer, buffer.len()))
		}
	};
}
pub use as_buffer;

and I get the same error because everything is inside the {} I guess

Just keep hoisting the buffer up.

#[macro_export]
macro_rules! as_buffer {
	($e:expr, $buffer:expr) => {
		{

			$e.serialize(&mut $buffer).unwrap()
			let buffer_pointer = $buffer.as_slice().as_ptr();
			Result::<(*const u8, usize), AsBufferError>::Ok((buffer_pointer, $buffer.len()))
		}
	};
}
pub use as_buffer;

Also keep in mind that you'll be getting a pointer to the serialized version of your struct. If you just want a pointer to your struct in memory you can skip the serialization step:

let object: Foo = ...;
let ptr = &object as *const Foo as *const u8;
let len = std::mem::size_of::<Foo>();

that's one way, but I wanted it to be more automatic than that :frowning:

What are you trying to achieve? Maybe if you give us more context of the problem being solved we can suggest alternative solutions.

I just wanted a frictionless way of getting a pointer. It's for a testing unit where rust passes the pointer to C++. I wouldn't use pointers for actual code. So, I have lots of test cases where I repeat that code inside the macro, and I wanted to use a simple solution.

let (pointer, len) = as_buffer(my_struct, Vec::new()).unwrap()

is ok but if it could be better, I'd like to try

A &MyStruct will automatically coerce to *const MyStruct. So why not just pass &my_struct to C++?

C++ expects a serialized object, which is not a struct serialization, but an actual file. I'm passing a file as a pointer.

You could leak each buffer.

macro_rules! as_buffer {
	($e:expr) => {
		{
			let mut buffer: Vec<u8> = Vec::new();
			$e.serialize(&mut buffer).map(|_| {
			    let len = buffer.len();
    			let ptr = buffer.leak() as *mut _ as *const _ as *const u8;
			    (ptr, len)
			})
		}
	};
}
1 Like

Getting a raw pointer in general is highly rare requirement in Rust. But you may have your own rare use case which requires it. To give you better suggestions we need to know why you want the raw pointers.

1 Like

Why are you using a raw pointer and not Vec<u8>?

You can pass a reference to C++ later with as_ptr(), while keeping ownership in Rust. (And making sure C++ does not keep references that outlive the call.)

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.