from catch22@programming.dev to rust@programming.dev on 07 May 11:33
https://programming.dev/post/29901284
Hello, I’m fairly new to Rust and came across this. Can someone explain to me how the following example is able to infer the constant value from the array length passed in? At this point, inferred type generation for function calls are a bit hand wavy, to me, does anyone know of a resource that breaks down all the different ways they can be used (for instance in this example I hadn’t seen them used for consts) and what their limitations are in Rust? I often run across a ‘this type can not be inferred’ error without really knowing why not and just throw in the type to make it go away.
Any other examples people could point me to would be appreciated as well.
Thanks!
#[derive(Debug)] struct Buffer<T, const LENGTH: usize> { buf: [T; LENGTH], } impl<T, const LENGTH: usize> From<[T; LENGTH]> for Buffer<T, LENGTH> { fn from(buf: [T; LENGTH]) -> Self { Buffer { buf } } } fn main() { let buf = Buffer::from([0, 1, 2, 3,5]); dbg!(&buf); }
Edit: for some reason, the code
markdown is hiding things inside of the <>'s (at least on my lemmy viewing client)
threaded - newest
It works because the size of an array in main() function is known at compile time. Arrays in Rust have fixed size, at that size is part of their type. The type of the array you pass to from() is
[i32; 5]
Got it, this completely made sense after your explanation and a second look. Also before I saw this example I hadn’t thought about being able to pass arrays and tuples as generic parameters types. Thanks
Technically, this may sound pedantic. You are not passing neither arrays nor tuples as generic parameter types.
What you are doing is passing an array to a function.
The type of the array is [i32;5]. Every value has a type.
By passing the array to a function, you are allowing the compiler to infer what function you are calling, since that function is generic. Using the type of the parameter you passed to it.
You can only pass values to function parameters. And you can only pass types as generic type parameters.
Well in this case it’s a little different, since it looks like you are passing a value (5) to a generic type parameter (LENGTH), but the
const
part ofconst LENGTH
means that it’s a value generic for a type, not a type generic for a type, which is the usual thing.EDIT: additionally, the
: usize
part tells you what type exactly the const parameter for the type has to be.Note that you can’t have non-const values as type parameters. Since types are defined at compile time.
EDIT 2: since type inference just fills some boilerplate for you. If we do that boilerplate manually it’s easier to see what parameters go where.
When you do
Buffer::from([0,1,2,3,4,5])
what you are really doing is:Buffer<i32, 5>::from([0,1,2,3,4,5)]
. In fact, if you put that, the code will compile exactly the same. Now if you put a 6 instead, it won’t compile since the type of the buffer and the type of the array you are passing are not the same.Since deadcream already told you the reason. I’m gonna explain a more generic way.
There are 2 important times: compilation time and run time.
At compilation time, everything that is constant, is known to the compiler, or can be calculated by it.
At run time, everything* is known.
Types have to be generated at compilation time**. This means that generics have to be also known at compilation time.
In this case. Both the “T” type of the buffer and its size “LENGTH” are generic, so they must be known at compile time. Compile time usually doesn’t know about vales of variables, except if those variables are “const”. Then it is known. A value literal is the same as a const variable.
So here, you provide a value literal ([0,1,2,3,4]) which is a fixed array, that is, both its “T” type (i32 by default) and length (5) are known at compile time. Buffer has all the information it needs to become a real type instead of a generic one. In this case, the type will be Buffer<i32, 5>
* Things that are optimized out at compile time are not known at runtime, but yes at compile time. For example:
Since A is never used (except to calculate B, which is const), A is probably optimized out. However, since B is used, there probably is a 6 somewhere in memory. Notice how I say probably since optimizations are optional. Or more optimizations may even remove the 6, and convert it to an ASCII “6” to be printed out.
**While this is always true trait objects (like Box<dyn ToString>) can act like some kind of runtime type, if you need that functionality.
I recommend using numbered footnotes (
¹
,²
etc.) or escaping the asterisk (\*
) instead of using plain asterisks for footnotes, because the asterisk is also used in Markdown for emphasis and list items.I gather from your explanation, that in order to tell before hand whether or not a type will be inferred, you really need to examine the code and see how things are being handled, and optimized out. (And even then you still may not know) Interesting, thanks.
You don’t need to know at all what optimizations will happen. I said that as an example of a thing that you know in compile time but not in run time.
To tell or not whether a type will be inferred is determined by you. If you tell the compiler the type, it will never be inferred. If you don’t tell the compiler the type, it will try to infer it. If it tries to infer the type but it fails, it will throw a compiler error and it won’t finish building the binary.
The compiler will only successfully infer a type if it has enough information at compile time to know with certainty what type it is. Of course, the compiler is not perfect, so it is possible in complex situations for it to fail even though it theoretically could.
Examples where inferring will succeed:
Examples where inference will fail