Pre-RFC: Function Variants

You can already use doc-comments on the impl blocks to create groupings.

Both of these are entirely possible with builders that make use of type parameters. For example, the following builder allows you to build any kind of Box that std currently offers, has almost no redundant methods (the redundant build()try_build() forwarding can be fixed by making try_build() into a trait method), does not allow any mutually exclusive options to be chosen, and does not have any run-time errors from incorrect builder use.

#![feature(allocator_api)]
#![feature(new_uninit)]
use std::{
    alloc::{AllocError, Allocator, Global},
    marker::PhantomData,
    mem::MaybeUninit,
};

pub struct BoxBuilder<I = (), A = Global> {
    initializer: I,
    allocator: A,
}

impl BoxBuilder<(), Global> {
    pub const fn new() -> Self {
        BoxBuilder {
            initializer: (),
            allocator: Global,
        }
    }
}

impl<I> BoxBuilder<I, Global> {
    /// Set the allocator the box will use.
    pub fn allocator<A>(self, allocator: A) -> BoxBuilder<I, A> {
        BoxBuilder {
            initializer: self.initializer,
            allocator: allocator,
        }
    }
}

/// Methods that determine the value type and means of initialization.
impl<A> BoxBuilder<(), A> {
    /// Set the initial value that will be moved into the box.
    pub fn value<T>(self, value: T) -> BoxBuilder<Init<T>, A> {
        BoxBuilder {
            initializer: Init { value },
            allocator: self.allocator,
        }
    }

    pub unsafe fn from_raw<T: ?Sized>(self, ptr: *mut T) -> BoxBuilder<FromRaw<T>, A> {
        BoxBuilder {
            initializer: FromRaw { ptr },
            allocator: self.allocator,
        }
    }

    pub fn uninit<T: ?Sized>(self) -> BoxBuilder<Mu<T>, A> {
        BoxBuilder {
            initializer: Mu {
                zeroed: false,
                len: (),
                _p: PhantomData,
            },
            allocator: self.allocator,
        }
    }

    pub fn zeroed<T: ?Sized>(self) -> BoxBuilder<Mu<T>, A> {
        BoxBuilder {
            initializer: Mu {
                zeroed: true,
                len: (),
                _p: PhantomData,
            },
            allocator: self.allocator,
        }
    }
}

impl<T, A: Allocator> BoxBuilder<Init<T>, A> {
    pub fn build(self) -> Box<T, A> {
        self.try_build().unwrap()
    }

    pub fn try_build(self) -> Result<Box<T, A>, AllocError> {
        Box::try_new_in(self.initializer.value, self.allocator)
    }
}

impl<T, A: Allocator> BoxBuilder<FromRaw<T>, A> {
    /// Build a box with Sized contents.
    pub fn build(self) -> Box<T, A> {
        // Safety: self.initializer.ptr was given to us via an unsafe method.
        unsafe { Box::from_raw_in(self.initializer.ptr, self.allocator) }
    }
}

impl<T, A: Allocator> BoxBuilder<Mu<T, ()>, A> {
    /// Make the box be of a slice of MaybeUninit with the given length.
    /// Whether it is zeroed is determined by the previous call to uninit() or zeroed().
    pub fn len(self, len: usize) -> BoxBuilder<Mu<T, usize>, A> {
        BoxBuilder {
            initializer: Mu {
                zeroed: true,
                len,
                _p: PhantomData,
            },
            allocator: self.allocator,
        }
    }

    /// Build a box containing MaybeUninit.
    pub fn build(self) -> Box<MaybeUninit<T>, A> {
        self.try_build().unwrap()
    }

    /// Build a box containing MaybeUninit.
    pub fn try_build(self) -> Result<Box<MaybeUninit<T>, A>, AllocError> {
        if self.initializer.zeroed {
            Box::try_new_zeroed_in(self.allocator)
        } else {
            Box::try_new_uninit_in(self.allocator)
        }
    }
}

impl<T, A: Allocator> BoxBuilder<Mu<T, usize>, A> {
    /// Build a box with a slice of a specified length, uninitialized or zeroed.
    pub fn build(self) -> Box<[MaybeUninit<T>], A> {
        if self.initializer.zeroed {
            Box::new_zeroed_slice_in(self.initializer.len, self.allocator)
        } else {
            Box::new_uninit_slice_in(self.initializer.len, self.allocator)
        }
    }
}

pub struct Init<T> {
    value: T,
}
pub struct Mu<T: ?Sized, L = ()> {
    zeroed: bool,
    len: L,
    _p: PhantomData<fn() -> T>,
}
pub struct Uninit;
pub struct Zeroed;
pub struct FromRaw<T: ?Sized> {
    ptr: *mut T,
}

I don't mean to claim that this exact builder is a good way to redesign the Box API (though it's been thought of, as I found out after writing the above and then noticing try_new_uninit_slice_in doesn't exist); only that builders can handle compile-time checking of both mutually exclusive and independent options, without any more code duplication than the problem actually demands.

4 Likes

Also, with recent RFC that allowed to make safe impls of unsafe trait we can make an unsafe trait for new and try_new and implement it safely when appropriate.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.