Name suggestion when new is taken

When working with non-default allocators, instead of writing say

let v = Vec::new();

you need to have something like

let type LVec<T> = Vec<T,Local>;

let v = LVec::new_in( Local::new() );

Here Local is a zero-size-type that implements Allocator and Default ( so is Global ). This gets a little tiresome, so it is convenient to have an “alternative” new function which uses the default, so you only have to write

let v = LVec::auto();

“auto” is what I thought of initially, it “automatically” creates the default allocator for you. I also have a method with_capacity_auto which is used instead of with_capacity_in. ( See here )

The trouble is “auto” isn’t that good of a name for this, but I am having trouble coming up with something better.

“new” cannot be used for backward-compatibility reasons, it always allocates always from Global, and changing that would mean a lot of changes to existing code (where the Allocator to be used is not specified by the type ). [ I tried then realised later it would not work well! ]

Originally I had a free function lvecbut that doesn’t look similar to Vec::new(), so I think it doesn’t read so well. The same applies to Box, Rc etc.

So I want a short word, that means roughly the same as new, but is reasonably distinctive. Can you think of something better than “auto”? Or is there a better approach altogether?

Some other words I am thinking of : “make”, “create”, “init”, “store”, “with”.

Why don't you leverage the Default trait then?

1 Like

Well auto does that.

    /// Create a new Vec with default allocator.
    #[must_use]
    pub fn auto() -> Vec<T, A>
    where
        A: Default,
    {
        Self::new_in(A::default())
    }

Am I missing something?

I suppose I could call it “new_in_default” instead of “auto” but that is a bit long to be convenient.

For some examples of how it is used, see maybe here, 32 uses of “auto” in one module:

or here, 15 uses in a module :

I think @firebits.io meant: why don't you implement the Default trait, and thus use ::default() instead of ::auto()?

2 Likes

Ok, well I think that would be incompatible, as “old” Vec implements Default, also it doesn’t work for Box, Rc, etc.

I see. When there are several ways to create an instance, I think it's reasonable to start looking at the builder pattern to avoid this headache of naming instantiation methods.

A different approach, I don’t know if this will work, call the new type something different, say VecA and have type aliases

type Vec<T> = VecA<T,Global>;

type LVec<T> = VecA<T,Local>;

type TVec<T> = VecA<T,Temp>;

Now when we say Vec we definitely mean the one allocated from Global, and Vec::new() is not ambiguous, so we can use new throughout. Am I thinking straight?

[ I am trying this out, I started with Box, and so far it looks good ]

Well it all seems to work nicely, and I no longer need “auto” at all, which is much better.

Here is Box and here is Vec.
And here is the doc for localalloc with examples to show how it works nicely:

Using Default seems to work just fine if you give your A type parameter a default value of Global.

Here is my Vec<T, A> implementation.

#![allow(unused)]
#![feature(allocator_api)]
use std::{
    alloc::{Allocator, Global, Layout},
    ptr::NonNull,
};

struct Vec<T, A = Global> {
    ptr: *mut T,
    len: usize,
    cap: usize,
    alloc: A,
}

impl<T, A: Allocator + Default> Vec<T, A> {
    pub fn new() -> Self {
        Self::default()
    }
}

impl<T, A: Allocator> Vec<T, A> {
    pub fn new_in(alloc: A) -> Self {
        todo!();
    }
}

impl<T, A: Allocator + Default> Default for Vec<T, A> {
    fn default() -> Self {
        Self::new_in(A::default())
    }
}

And Local is just a stub:

#[derive(Default)]
struct Local;

unsafe impl Allocator for Local {
    fn allocate(&self, _: Layout) -> Result<NonNull<[u8]>, std::alloc::AllocError> {
        todo!()
    }
    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {
        todo!()
    }
}

Then the usage looks like this:

type LVec<T> = Vec<T, Local>;

fn main() {
    let using_global = Vec::<u32>::default();
    let using_global_and_new = Vec::<u32>::new();
    let using_local = LVec::<u32>::default();
    let using_local_and_new = LVec::<u32>::new();
}

(playground)

Implementing a fn new() constructor when A: Allocator + Default seems to work as expected. I'm guessing the A=Global default means there's no ambiguity as to which allocator to use when you call Vec::new().

It works when you have a turbofish, but not if you do not, when you get “type annotations needed” error.

So

fn main() {
let using_global = Vec::default();
let using_global_and_new = Vec::new();
let using_local = LVec::default();
let using_local_and_new = LVec::new();
}

will not compile ( even when you push something which allows element type inference ). That is the heart of the compatibility problem. That is why I want Vec (without any turbofish) to always mean Vec allocated from Global. A type alias seems to do that nicely.

This only works because you annotated the types – Vec::<u32> means Vec::<u32, …fill in the defaults…>, but plain Vec does not unfortunately. Aria Desires has a post on "Defaults Affect Inference in Rust", which discusses the topic.

Concretely, take this modified playground here – if you add

impl<T, A: Allocator> Vec<T, A> {
    // ...

    pub fn push(&mut self, item: T) {
        todo!();
    }
}

and then add calls to push in main but take out the type annotations:

fn main() {
    let mut using_global = Vec::default(); // Fails!
    using_global.push(0u32);
    let mut using_global_and_new = Vec::new(); // Fails!
    using_global_and_new.push(0u32);
    let mut using_local = LVec::default();
    using_local.push(0u32);
    let mut using_local_and_new = LVec::new();
    using_local_and_new.push(0u32);
}

then Vec::default and Vec::new both fail:

error[E0283]: type annotations needed for `Vec<u32, _>`
  --> src/main.rs:40:9
   |
40 |     let using_global = Vec::default();
   |         ^^^^^^^^^^^^   --- type must be known at this point
   |
   = note: the type must implement `Allocator`
   = help: the following types implement trait `Allocator`:
             &A
             &mut A
             Arc<T, A>
             Box<T, A>
             Local
             Rc<T, A>
             System
             std::alloc::Global
note: required for `Vec<u32, _>` to implement `Default`
  --> src/main.rs:31:33
   |
31 | impl<T, A: Allocator + Default> Default for Vec<T, A> {
   |            ---------            ^^^^^^^     ^^^^^^^^^
   |            |
   |            unsatisfied trait bound introduced here
help: consider giving `using_global` an explicit type, where the type for type parameter `A` is specified
   |
40 |     let using_global: Vec<_, A> = Vec::default();
   |                     +++++++++++

It works for LVec because LVec fixes the allocator, and this is why @geebee22's working solution has to make Vec an alias for a different underlying type.

1 Like