Skip to content

DRAFT: Support Buffer on &mut MaybeUninit<T> #1413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

joshtriplett
Copy link
Member

This returns an output of Option<&mut T>: Some for .assume_init(1),
and None for .assume_init(0).

This is a draft because it's not clear yet whether Option is the best
possible return value. For syscalls that will always initialize the
buffer if they return successfully, this isn't ideal, because it
requires handling an Ok(None) case that'll never happen. However, for
syscalls that can return zero output successfully, like read, we'd
need some way to handle that case.

This returns an output of `Option<&mut T>`: `Some` for `.assume_init(1)`,
and `None` for `.assume_init(0)`.

This is a draft because it's not clear yet whether `Option` is the best
possible return value. For syscalls that will *always* initialize the
buffer if they return successfully, this isn't ideal, because it
requires handling an `Ok(None)` case that'll never happen. However, for
syscalls that *can* return zero output successfully, like `read`, we'd
need some way to handle that case.
@sunfishcode
Copy link
Member

Which system calls are you envisioning using this with?

@joshtriplett
Copy link
Member Author

@sunfishcode Anything where you're getting back a single data structure and don't want to unnecessarily initialize it. By way of example, suppose we added wait4; the fourth argument could be &mut MaybeUninit<RUsage>.

I drafted this because it seems more convenient than using &mut [MaybeUninit<T>; 1] and getting back two arrays of size 1 and 0 or 0 and 1. My thought was "that seems painful compared to Option".

Suggestions welcome for better ways to structure this.

@sunfishcode
Copy link
Member

What if we introduced a separate trait for those cases? Perhaps something like:

pub trait OptionalOut<T> {
    type Output;
    fn as_mut_ptr() -> Option<NonNull<T>>;
    unsafe fn assume_init() -> Self::Output;
}

This would more precisely capture the subtleties here: for this use case: the Output wouldn't need to be Option, and we wouldn't implement the trait for &mut [T; N], because there will always be exactly one value (unless the function fails, but in that case you don't get an Output at all).

@sunfishcode
Copy link
Member

Then we could have:

pub fn wait4<Out: OptionalOut<Rusage>>(
    pid: Option<Pid>,
    waitopts: WaitOptions,
    rusage: Out
) -> Result<Option<Out::Output>>

The Option in the result is to handle the case where the user doesn't want the Rusage value and passes in a value for which as_mut_ptr returns None.

But seeing that written out, I wonder if this wouldn't be better to just have separate functions, one that doesn't return the Rusage like we have today, and one that does. That way users wouldn't ever have to deal with a redundant Option.

@joshtriplett
Copy link
Member Author

@sunfishcode I've been assuming that we'll only offer wait4 for the case where you pass a valid rusage pointer, because if you don't want the rusage, there's no reason to call wait4. The only scenario where it might need an Option there would be if called with WNOHANG, and in that case the entire return value (PID included) should be an Option.

The reason for the Option was trying to make this fit into Buffer, because if you were to pass this to e.g. read, that can do a 0-length read if it's at EOF, in which case it won't have initialized the value. If this doesn't use Buffer, then it doesn't need Option.

@joshtriplett
Copy link
Member Author

joshtriplett commented Mar 12, 2025

Also, to answer another aspect of your point...

I would like the solution for e.g. wait4 to:

  1. Not require initializing the rusage struct before calling wait4.
  2. Not require copying the rusage struct after the syscall returns; I would ideally like to read into a stack buffer in the caller.
  3. Not require unsafe code.

If we accept &mut RUsage, that breaks point 1. If we return RUsage by value, that breaks point 2.

It may be that I should give up on one or the other of those two points. It would certainly be easier to either initialize the RUsage struct or return it by value. (Of the two, I think the latter would be easier.)

And if the function is inlined and the optimizer does a good enough job, in theory that should elide the copy and act as if it was allocated in the caller, such that if you just read a couple of fields out of it you don't end up copying the rest of the structure.

So, maybe the right answer for wait4 in particular is:

pub fn wait4(pid: Option<Pid>, waitopts: WaitOptions) -> Result<Option<(Pid, WaitStatus, RUsage)>>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants