Alloc: Vec::as_slice, String::as_str -> const

I'm writing a library that wants Vec::as_slice and String::as_str to be const-qualified -- the types I'm trying to produce are enums that may contain either Vec<T> or &[T] (and similarly String or &str), and I'd like to be able to write a const fn as_slice() / as_str() for them.

Presently, this isn't possible because the stdlib functions I need aren't const, but logistically, changing them (or writing _const-suffixed versions) is trivial -- I've written a local liballoc patch to enable the functionality I'm looking for and tested it successfully with my library -- is this likely to be viewed favorably for upstreaming? Would a change like this require an RFC? Are there problems with this approach re: const-ness I haven't seen/understood?

Library motivation, for anyone interested

The library is in a similar spirit as managed (and Cow, in some sense) with the intent of abstracting static vs dynamic allocation of immutable data, supporting #![no_std] environments that may or may not have alloc. I have an enum ImmutSlice<'t, T> which can be Slice(&'t [T]) or Vec(Arc<Vec<T>>) (plus ImmutStr<'s> for Arc<String>/&'s str).

I think the most concise way to put the idea is that the types support upgrading to 'static where required, by cloning the borrowed variant into Arc, but do not need to leak() to do so -- the allocation can be recovered when all references go out of scope.

1 Like

Can you currently create a non-empty Vec/String as a const? For empty values you can use &[] and "" respectively without helpers, but I'm not sure if non-empty ones are possible.

The only reason to keep a function not const is if in the future it might need to do something that can’t be const. That seems unlikely for the ones you named, so I’d say go for it.

No, but to be stably const it will need approval from T-libs-api — which can happen on the PR that makes the change, so you don't need to do anything special.

I'm not sure whether it would be required to be unstable const before stabilization; presumably yes but I'm not familiar with the design principles in that area.

2 Likes

Something as trivial/uncontroversial as this could be done without being unstable imo. Not a team member, though.

I'm not opposed, but I also don't really understand the use case here. How are these useful in const contexts?

I don't think this would play well with the Store API RFC. Under that proposal as_slice would have to resolve the handle, but that's a trait method and at least for a while they won't be able to be const (and even then, not all resolves might be consts).

1 Like

This code doesn't compile, but it would if String::len() was a const fn:

use std::borrow::Cow;
const fn length(s: &Cow<'_, str>) -> usize {
    match s {
        Cow::Borrowed(s) => s.len(),
        Cow::Owned(s) => s.len(),
    }
}

So, making these functions const allows writing functions that can be used in const evaluation on enums that may contain run-time-only values, but of course won't actually contain them at compile time.

In general, if both

  1. You can make it const without changing the implementation (no rewriting for loops, for example), and
  2. There are ways to actually make the arguments in const

then you can just send a PR making it unstably const and it'll get signed-off on easily.

Making it stable for const is a harder question, but one that can be deferred a bit.


The why for those two cases are basically their contrapositives:

  1. We decided to stop making the implementation ugly just to support const. As such, we're usually waiting on things like impl const before making things that depend on traits be const.
  2. We don't want PRs for every single function that could possibly be marked const, since that's just noise if the const-ness wouldn't ever be useful for anyone.