Too many similar functions

I'm working on a library for an arena-like container structure.

The structure has the following functions:

  • reserve - same as Vec::reserve, but argument is bytes.
  • reserve_exact - same as Vec::reserve_exact, bytes.
  • reserve_layout - reserve with alignment
  • reserve_layout_exact
  • try_reserve - returns Result instead of panicking
  • try_reserve_exact
  • try_reserve_layout
  • try_reserve_layout_exact

I wrote an underlying implementation that handles all cases and they are implemented as "wrappers" for it, but I'm left wondering:

  • is this the best way to approach things? if not, what would you do differently?
  • do I copy-paste the documentation with minor adjustments or just link all of them to a single, well-documented one with examples?

I've rarely seen people implement more than 2/3 variants of the same function in the wild so the thing I'm doing seems weird/wrong. At the same time, there are use cases where it's more convenient to call each one and the base implementation would be too verbose in majority of use cases:

fn ensure_free_section<const EXACT: bool>(
    &mut self,
    required: usize,
    align: Option<usize>,
) -> Result<Option<MemoryBase>, MemoryError> { ... }

The focus of the library is being able to finely control the memory footprint of that container, but it still feels like I might be trying to provide too many ways to do a similar thing.

1 Like

I assume this allocates items of different types and sizes like bumpalo, rather than a single type like slabs. If so, I don't think you can reduce the number of methods and you may need to add more. :slight_smile: In an ideal world we could allocate anything with an arena, but we need the new allocator API for that.

Yup, it works similarly but uses smart references to free chunks once the references go out of scope: contiguous_mem/examples/default_impl.rs at dev/0.5 · Caellian/contiguous_mem · GitHub (prev. version docs)
It's not perfect yet (last version grows too little, but that's a minor bug), it's working however and doesn't require a complete clear to reallocate.

It's not an allocator because the allocator api isn't stable yet, so I wrote it as a container.

My primary question I guess is how to document all of those different variants (for the next version) given that their descriptions overlap for the most part.

For things like this, documentation will often just say something like:

/// Reserves at least `n` bytes.
///
/// This is equivalent to calling `self.try_reserve_layout(n, 1).unwrap()`.
/// See [`try_reserve_layout`] for more.
fn reserve(&self, n: usize)

Then you only need full documentation on try_reserve_layout and try_reserve_layout_exact.

4 Likes

In that case I would make the API exactly the same as the current Allocator's API, so it can be easily transitioned when Allocators are stable.

I don't think the allocator api will suffice - it doesn't currently as it's really a container (heterogeneous Vec). The api has no way of specifying which allocator to use on a per-item basis (allocator composition?). I copied the proposed trait and it's implemented for Allocator on nightly though, and will probably remove that trait with the std one once it hits stable - it's a polyfill.

You could try using a builder/fluent API but it's pretty boring code to write and not much nicer to use.

This might look like container.reserve_with().layout(..).exact(), etc, but naming in this case seems hard.

1 Like

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.