Skip to content
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

Give ArrayVec a const-fn constructor #93

Closed
michael-p opened this issue Feb 2, 2018 · 17 comments · Fixed by #181
Closed

Give ArrayVec a const-fn constructor #93

michael-p opened this issue Feb 2, 2018 · 17 comments · Fixed by #181

Comments

@michael-p
Copy link

I'm not sure if this is even possible but it would be great if ArrayVec::new() would be a const fn and hence could be called in places where only constant expressions are allowed (of course behind some feature-flag since const-fn is still unstable).

The reason I'd like this feature is that I work on embedded systems where it's typical to have all memory allocated statically, i.e. for a large buffer one would have something like

static mut BUFFER: [u8; 4096] = [0; 4096];

This works fine for arrays (and Copy types) but it would be great if I could also use an ArrayVec there. Again, not sure if it's even possible since mem::uninitialized() seems to not be a const-fn (although it should, I think) but maybe/hopefully I'm wrong. :)

lazy_static! will not work AFAIK because on first invocation of the initialization code the value gets constructed on the stack which typically will not be large enough to hold that value, even temporarily.

Thanks for considering this! :)

@bluss
Copy link
Owner

bluss commented Feb 6, 2018

It's an attractive idea -- maybe it needs to be a separate constructor. I don't know when uninitialized will be a const function — for a lot of usage, that function doesn't matter for const eval, because we don't mind computing actual initialization in const eval.

Also note, that defining const fn is an unstable feature.

@bluss bluss changed the title Make ArrayVec::new() a const-fn Give ArrayVec a const-fn constructor Feb 6, 2018
@bluss
Copy link
Owner

bluss commented Feb 10, 2018

I've explored this in more detail. There's a lot that blocks it at this point:

  • A "ManuallyDrop" that is const-constructible (requires unstable feature untagged_unions to implement manually)
  • Index::from constructor of the index trait must be const but that is not implemented, "trait fns cannot be const"

it does not look like it can be implemented close to the present

@michael-p
Copy link
Author

Ok thank you for digging deeper into this! If you think there is no way to do this in the near future than feel free to close this issue.
I'm currently investigating another "hacky" work-around using placement-in but might also switch to standard arrays instead if that does not work, not sure yet.

@bluss
Copy link
Owner

bluss commented Feb 12, 2018

If you have a Copy-only element it gets a lot easier (the implementation is also mostly free of pitfalls, so it should be more straightforward). The problem with Index::from was a bit of a surprise here though.

@michael-p
Copy link
Author

Actually in my use case the elements could be Copy, yes (they aren't right now, but for semantic reasons). But wouldn't implementing that require specialization in order to distinguish between Copy/Non-Copy types?

@bluss
Copy link
Owner

bluss commented Dec 1, 2018

Revisit for Copy-only types (For example ArrayString is backed by a copy array.)

We still have this issue in nightly:

error: trait bounds other than `Sized` on const fn parameters are unstable
  --> src/array_string.rs:45:6
   |
45 | impl<A> ArrayString<A>

So a const fn new for ArrayString is still not possible, based on that, even independent of the problem with the Index trait for the type of the length field.

@michalfita
Copy link

What's the benefit of static ArrayVec? I thought ArrayVec is useful for storage items up to certain limit. A static variable in Rust cannot be changed, hence no benefit, and mutable statics are unsafe. Could you explain @michael-p?

@michael-p
Copy link
Author

You would need to use interior mutability, for instance with a Mutex. So you might have something like:

static BUFFER: Mutex<ArrayVec<[SomeType; 1024]> = Mutex::new(ArrayVec::new());
...
BUFFER.lock().push(my_value);

Note that std::sync::Mutex::new() is also not (yet?) a const-fn so the example would not work with that, but you could e.g. use the spin crate which has a Mutex with const-fn new (that's what's used in the embedded world, where my initial use-case came from).

@bluss
Copy link
Owner

bluss commented Jan 4, 2019

We want an ArrayVec for Copy elements eventually #32, seems there is no way around that, and it should be able to have const fn constructor exactly when ArrayString can, so the issue mentioned 2 comments back also blocks that, just to note.

@michalfita
Copy link

I do embedded programming either, most of my professional career... at these global arrays smells 80's like C-style globals I thought are long gone, @michael-p. I don't denounce the need for const fn constructor for ArrayVec, but the given example is far from good practice to me. I wonder why with new great language as Rust is old habits from C still come into play?

@jacobb11
Copy link

jacobb11 commented May 6, 2020

I would also like the ability to construct an ArrayVec in a static.

I have a program with a bunch of small immutable assets, and I would like to make them statics. Currently I'm constructing them all at startup, but it would be nice to build them offline, write them out as Rust statics, and then just use that.

Several of them contain Vec-s, which is handy when building the assets, but an ArrayVec would work just as well since I know that maximum size, which is quite small.

I think this use case requires only something like:

impl ArrayVec {
pub const fn ArrayVec::static_new(a: &A, len: usize) -> Self { .. }
}

I'm happy to specify a value for every array element, even though only "len" of them will be used. I don't know if that eases implementation. Since the ArrayVec is immutable static it will never be modified, which may/hopefully alleviate the question of whether the unused elements are initialized.

@bluss
Copy link
Owner

bluss commented May 6, 2020

It seems like const generics might be our only way to get to this (of course not stable yet); because const fn's by themselves won't support our array length trait.

@jacobb11
Copy link

jacobb11 commented May 6, 2020

Any chance of adding ArrayVec::resize_with? I just replaced a Vec with an ArrrayVec, and it was so close to just... change the declaration and the one initialization... except I also had a resize_with, and I had to rewrite that by hand. But it's trivially implementable in terms of push & pop.

(If offering a code change would help, I can probably do that.)

@bluss
Copy link
Owner

bluss commented May 6, 2020

Need to think about how to handle the bounds check for new length (what's consistent - probably panic for out of bounds of capacity), other than that, PRs welcome for resize_with

@rodrimati1992
Copy link
Contributor

rodrimati1992 commented Mar 28, 2021

The constructor can be rewritten to a const fn by doing this on stable:

    pub const fn new() -> ArrayVec<T, CAP> {
        assert_capacity_limit!(CAP);
        ArrayVec { xs: Self::__UNINIT_ARRAY, len: 0 }
    }

    const __UNINIT_ELEM: MaybeUninit<T> = MaybeUninit::uninit();
    const __UNINIT_ARRAY: [MaybeUninit<T>; CAP] = [Self::__UNINIT_ELEM; CAP];

but it would require hackily changing the assert_capacity_limit macro to:

macro_rules! assert_capacity_limit {
    ($cap:expr) => {
        if std::mem::size_of::<usize>() > std::mem::size_of::<LenUint>() {
            if $cap > LenUint::MAX as usize {
                [/*ArrayVec: largest supported capacity is u32::MAX*/][$cap]
            }
        }
    }
}

which gives you this error in const contexts:

error[E0080]: could not evaluate static initializer
  --> src/lib.rs:40:17
   |
40 |                 [/*ArrayVec: largest supported capacity is u32::MAX*/][$cap]
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                 |
   |                 index out of bounds: the length is 0 but the index is 4294967296
   |                 inside `ArrayVec::<u8, 4294967296_usize>::new` at src/lib.rs:40:17
   | 
  ::: src/arrayvec.rs:62:57
   |
62 | static ARRBVEC: ArrayVec<u8, {u32::MAX as usize + 1}> = ArrayVec::new();
   |                                                         --------------- inside `ARRBVEC` at src/arrayvec.rs:62:57
...
83 |         assert_capacity_limit!(CAP);
   |         ---------------------------- in this macro invocation
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

and this error at runtime:

thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 18446744073709551615', /src/arrayvec.rs:81:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string

Don't know if you're OK pessimising the runtime error for the constructor to make it a const fn.

What the associated constants are doing is using the new-ish feature to initialize arrays by repeating consts,
described in the release notes for Rust 1.50.0

You can now use const values for x in [x; N] array expressions. This has been technically possible since 1.38.0, as it was unintentionally stabilized.

@bluss
Copy link
Owner

bluss commented Mar 28, 2021

@rodrimati1992 I thought that should give you an error that it can't use generic T from the outer function, is that not the case? It's because it's an associated const? Looks like a good solution in that case. No __ is needed - this item is private anyway.

@rodrimati1992
Copy link
Contributor

rodrimati1992 commented Mar 28, 2021

@rodrimati1992 I thought that should give you an error that it can't use generic T from the outer function, is that not the case? It's because it's an associated const? Looks like a good solution in that case. No __ is needed - this item is private anyway.

Associated constants can use the generic parameters of the impl block.

Example:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9a8078a6c4011ee6e9875efaa0d105cf

struct MakeArray<T, const LEN: usize>(T);

impl<T, const LEN: usize> MakeArray<T, LEN> {
    const INNER: [T; 0] = [];
    pub const MAKE: [[T; 0]; LEN] = [Self::INNER; LEN];
}

fn main(){
    println!("{:?}", MakeArray::<u8, 10>::MAKE);
}

prints:

[[], [], [], [], [], [], [], [], [], []]

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 a pull request may close this issue.

5 participants