Idea: array range [T;N..M] to help with returning arrays of different lengths

I make this thread motivated by these conversations

Proposal

Add a array type [T;A..B] meaning an array of elements of type T with size in the range A..B. And let coercions from the array [T;N] when N is in the range. This would allow to write functions that aim to return arrays of different sizes. And I think it would be a more conservative approach than returning the unsized [u8] as proposed in the other threads.

fn foo(input:bool) -> [u8;4..6]{
	if input{
		[1,2,3,4,5]
	} else {
		[2,4,6,8]
	}
}

It would also help to write Cows to to arrays with a known upper bound on their size.

fn foo_cow<'a>(input:bool, else_ref:&'a [u8;4..6]) -> Cow<'a,[u8;4..6]>{
	if input{
		Cow::Owned([1,2,3,4,5])
	} else {
		Cow::Borrowed(else_ref)
	}
}

Then a proper Return Value Optimization could create these data directly in the caller stack, at discretion of the compiler.

Issues

Its implementation would require to store the size. I think the [T;N] contains just the N elements and that &[T] is a size plus pointer. This puts [T;A..B] in a awkward position, as it would store B-A+1 elements plus the size. And then &[T;A..B] would be a single pointer. So both conversions [T;N] into [T;A..B] and &[T;A..B] into &[T] would require a little work.

Notation and alternatives

We could admit to use [T;A..=B]. It seems way more useful the upper limit, so we may want to omit A, allowing always to include 0-sized arrays. We could write this as [T;..=B].

Even a type without the coercions from [T;N] could be useful, although it would be more verbose. For example it would be nice to be able to define SliceUpTo<N> for arrays of length at most N.

Const-generics

This would seem to have much relation with the const-generics feature, which I am not sure in what state is currently implemented. It almost seem that we should be able to implement it as user. But I find myself failing to implement it as follows.

struct SliceUpTo<T,const N:usize>{
	size: usize,
	data: [T;N],//And just use some part via transmutes?
}
impl<T,const N:usize> SliceUpTo<T,N>{
	fn new<M:usize>(value:[T;M]) -> SliceUpTo<T,N>
		where M <= N // <-- We cannot write this
	{ todo!(); }
	fn into_array(self) -> [T;M] // <-- This should require M=self.size, which is way too much.
	{ todo!(); }
	fn as_ref(&self) -> &[T] // <-- Although perhaps `into_array` is not required and just implement `as_ref`.
	{ todo!(); }
	fn as_mut(&mut self) -> &mut [T]
	{ todo!(); }

}

Final comment

Question for the authors of the two linked threads. Would this approach be satisfying enough for your use-cases?

IMO this is exactly what should be the case. I’m not too up to date on the current state of const generics, but my feeling is that this type does not deserve any special syntax or compiler magic but should be user-definable. (I.e. if it isn’t possible already, then const generics should evolve further so that it everntually is user-definable.)

And in fact it should be user-defined in a crate and proven useful before one would even start discussing whether it needs to get included in the standard library.

Edit: Something similar to your proposed [T;..=B] type, without const generics though, seems to be implemented already in the arrayvec crate.

2 Likes

We're going to end up with something like arrayvec in core, so my first instinct is that that's the correct way to handle this -- nightly already has something very similar by returning an array::IntoIter<T, 6>.

Related conversation if this is about the semver-ness of the length being returned: https://github.com/rust-lang/rfcs/pull/2545#issuecomment-661305546

2 Likes