Cycle detected when building an abstract representation

Not sure whether this from design or confused since not understanding error. The where [MaybeUninit<T>; Self::test()]: Sized so that const generics are constrained. New is to show that this is not Default specific behavior, as suggested elsewhere.

pub struct SliceVec<T: Default, const N: usize> 
    {
    len: usize,
    buff: [MaybeUninit<T>; N],
}

impl<T: Default, const N: usize> SliceVec<T, N> 
    where [MaybeUninit<T>; Self::test()]: Sized,
    T: Default

    {
    const fn max_len() -> usize {
        N
    }
 
    const fn with_bound() -> [MaybeUninit<T>; Self::test()] 
            where [MaybeUninit<T>; Self::test()]: Sized,
            T: Default
        {
        [MaybeUninit::new(T::default()); Self::test()]
    }
    
    const fn test() -> usize {
        (C* (N as f64) + C0) as usize
    }
}

trait Tr<T: Display + Default, const N: usize> {
    fn f(v: &SliceVec<T, N>) {}
}

pub struct F<T: Default, const N: usize> {
    pub s: SliceVec<T, N>,
}


impl<T: Display, const N: usize> Tr<T, N> for F<T, N> 
            where [MaybeUninit<T>; SliceVec::<T, {N}>::test()]: Sized,
            T: Default

    {
    fn f(v: &SliceVec<T, N>)
        where [MaybeUninit<T>; SliceVec::<T, {N}>::test()]: Sized,
        T: Default
    {

        let mut V: SliceVec<usize, {SliceVec::<usize, {N}>::test()}> = SliceVec {
            len: N,
            buff: SliceVec::<usize, {N}>::with_bound()
        };
        
        let mut W: SliceVec<u8, {SliceVec::<u8, {N}>::test()}> = SliceVec {
            len: N,
            buff: SliceVec::<u8, {N}>::with_bound()
        };
        
    }
}

Self::test() is only available if [MaybeUninit<T>; Self::test()]: Sized which however requires Self::test() to be available and you get a cycle.

Self::test() is also necessary to have const expression with generic parameter though. The question is whether this not allowed in const_evaluatable_checked by design (e.g. Default) or if there is a better way to design SliceVec such that generic bounds are unconstrained. related issue and previous thread

Can't you just move it outside the impl block? Rust Playground

Perhaps not understanding, but did you send the correct one?

I forgot to specify it still doesn't compile because MaybeUninit::new(T::new()) is not const, nor MaybeUninit<T> : Copy, however it fixed the cycle error.

How would you handle MaybeUninit<T> : Copy?

Edit: For context:
core::marker::Copy cannot be made into an object because it requires Self: Sized. note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically

I though of a couple of ways, however all of them are currently not possible or viable:

  • Require T: Copy, which however is really limiting
  • Wait for RFC 2203 (const_in_array_repeat_expressions) which however is not fully implemented
  • Initialize the array with MaybeUninit::uninit() and then populate it with calls to T::new(). However this is not currently possible since MaybeUninit::uninit_array and MaybeUninit::assume_init aren't const
  • Swap the [MaybeUninit<T>; test::<{N}>()] for a MaybeUninit<[T; test::<{N}>()]>. You will be able to create it, but you won't be able to initialize each member with T::new()

IMO this will eventually be possible, however in the current state these features are not stable enough.

ps:

Looks like you're trying to use Copy as a trait object, which is something you probably don't want to do.

Require T: Copy

As you say, this is too limiting

RFC 2203

Unclear how features move to nightly, does that there is a way to import features not in const_in_array_repeat_expressions?

MaybeUninit::uninit_array and MaybeUninit::assume_init aren't const

Yes.

each member with T::new()

What does this mean? The question asks about instantiation with default/new. How are you suggesting to instantiate?

Looks like you're trying to use Copy as a trait object, which is something you probably don't want to do

Yeah, just making sure you were not suggesting this.

IMO this will eventually be possible, however in the current state these features are not stable enough

This is helpful, but also seems to indicate that moving Self::test() outside is probably not the strategy, right? If waiting for features, it is likely better to wait for const generic parameters that depend on associated constants, as @RustyYato suggests. Moving Self::test() outside seems to suggests Cycle detected when building an abstract representation is an intentional design choice from rustc, which seems unlikely how const_evaluatable_checked and unconstrained generic constant lead to this design. As mentioned above, this design does work until with_bound is generic.

Do you perhaps understand why generic T makes cycle? Perhaps still misunderstanding, but otherwise seems the link and solution in previous thread is incorrect, or there is another way to design SliceVec.

If it has something to do with Default, why does New not work?

If you want to learn how to build a vector backed by an array with const-generics, I suggest you look at staticvec.

Someone needs to implement them.

I'm saying that currently there's no way to instantiate them.

You're thinking you have one problem, but that's wrong, you have multiple problems. Moving Self::test() outside solved the first problem, but you also have a second and a third problems that are unrelated to the first.

It's not about having a generic T, but having test() be defined inside a block that requires test() to exists. Defining it inside the block is like asking if the sentence "this sentence is true only if this sentence is true" is true, which is ambiguous.

To make this clearer, consider this example. It gives the same error as when you moves Self::test outside, however now there's no Self::test, it's just a simple function. Trying to swap New() with Default() gives the same error. Remove the generic parameter and call a non-const function and you will still have the same error. It's not about associated constants, it's just about what functions you can call in a const enviroment and what you can use to initialize an array.

Default is not special in the language, it's just another user defined trait like New. Both have the problem that the function they define can't be const, and as such can't be called in a const function like with_bound.

Note that @RustyYato suggested staticvec, which is fine and probably the best you can write right now, however it still can't initialize its elements in a const environment like you're trying to do.

You can initialize elements usimg a macro in a const environment, but that's a bit different.

It is not as much above how learning how a vector backed by an array with const-generics works as it having the elements in a const like @SkiFire13 says. This is why s is a field of F

pub struct F<T: Default, const N: usize> {
    pub s: SliceVec<T, N>,
}

as opposed to just implementing f on top of SliceVec. Pretty sure swapping SliceVec and StaticVec should both cycle detected when building an abstract representation. Confirmed unconstrained generic constant.

edit: as you say, staticvec![T;N] is a bit different.

inside a block

How does this compile? Get your point that the first where clause is a definition, but, given the way the rustc messages lead to this design (as well as the ones in this link), thought there should be another way (the compiler is suggesting Cycle detected when building an abstract representation by design) Maybe you are right, but then rustc messaging is a bit misleading.

Assuming that case, do you think swap the [MaybeUninit<T>; test::<{N}>()] for MaybeUninit<[T; test::<{N}>()]> and matching on T instead trait for instantatiation?

Default is not special in the language, it's just another user defined trait like New . Both have the problem that the function they define can't be const , and as such can't be called in a const function like with_bound

May be misunderstanding, but you are saying that default type [u8; std::mem::size_of::<T>()]> decision on generic consts in the default type of a parameter is to mirror no const fn in trait?

Because:

  1. test::<{N}>() is no longer present in the bounds of the block that define test (which leads to the cycle)
  2. MaybeUninit::new(0) is a const expression (MaybeUninit::new is a const function and 0 is a const expression)
  3. MaybeUninit<usize> implements Copy

Previously we fixed point 1 by moving test outside the impl block, but 2 and 3 remained because they derive from using a generic T

Your messages are a bit confusing, but maybe you want an associated function/const such that [MaybeUninit<T>; test<{N}>()] is well formed? You can probably get away with just declaring and implementing test in a block without the well formed bound, and everything else in another block with the bound.

Yes to using a MaybeUninit<[T; test::<{N}>()]> but I don't understand what you're meaning with "matching on T".

You're probably confusing different defaults.

A default type is like T=Bar in Foo<T = Bar>, that is a generic parameter which becomes that type if not specified (e.g. Foo would be the same as Foo<Bar>, but for any other T you would have to write Foo<T>). There's no default type in any playground you posted.

Another thing is the Default trait, which is a way to define the property of a type to have a default value (e.g. Vec implements Default because it has a default value, e.g. the empty vector). This is completly different from the default type in generics.

1 Like

block without the well formed bound

Would you mind explaining this more? Thought you meant using a wrapper trait, but again const fn in trait.

matching on T

This is just instead of using T::default() having custom instantiation for a fixed set of types

You're probably confusing different defaults

Yes, thank you!

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.