Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Something I can't figure out is how it infers the length in this example:

  let array = core::array::from_fn(|i| i);
  assert_eq!(array, [0, 1, 2, 3, 4]);
Is it because of the assert_eq? Can it use the length of the second argument to infer the length all the way back up in the from_fn call?


Yes, it's inferring that `array` must be an `[i32; 5]` because there exists an impl of `PartialEq` for `[T; N]` against another `[T; N]`, so `N` in `::from_fn` must be 5.


Sorry but the typing part is incorrect. Look at the signature of from_fn:

    pub fn from_fn<T, const N: usize, F>(cb: F) -> [T; N] 
    where
        F: FnMut(usize) -> T, 
It takes a function with signature f(usize) -> T. So |i| i gets inferred to be a function taking a usize, and returning a usize (since we just give the argument straight back). So it infers [usize; 5] in the end.

For example core::array::from_fn(|i| i) == [0i32] wouldn't even compile.


You are right. The parameter given to the closure is the array index, which is usize. Since that's returned straight back the returned value is also of type usize, and the integers in the array in the assert are inferred to also be usize.

Which is in a way an even stronger showcase of Rust's type inference: the length of the variable is inferred from the constant array, but the type of the content of the constant array is inferred by the type of the content of the variable, which is inferred from the type of the closure.


Good catch. The parent comment is interested in how the array length is inferred though, so I'm going to consider the explanation close enough (that it's an i32 or usize isn't particularly relevant).


What's a sane/legit use case of such inferring in general?


It's not anything specific to arrays, it's just how type inference works. Here's another example:

    let mut x = HashMap::new();
    x.insert(1, 2);
Note that nowhere did we need to specify the concrete type of the HashMap; the compiler sees that you eventually insert numbers into the map so it uses that information to fill in the generic type parameters for the key and value.


I think the one here is legit (perhaps more realistic would be where you're passing the new array to an actual function that requires a certain array length).

People always complain about Rust's type system being verbose- well, this makes it less verbose. It knows what you need from the function call, so it doesn't make you say it. And as an added benefit, if the required type ever changes in a way that can still be generated by the provided code (eg. the function you're passing to needs a different length), you won't need to update any explicit type annotations at the call-site.

If that's ever undesirable - you want to be very precise about your code - well, then you can do that too by specifying the generic parameters explicitly.


  let numbers = [0, 1, 2];
  let doubled = numbers.map(|x| x * 2);
You wouldn't want to have to explicitly type out the length of the second array.


That isn't really inferring the size of the array though. [T; N]::map(impl FnMut(T) -> U) defines its return type as [U; N]. N is fixed at the original array literal.

https://doc.rust-lang.org/std/primitive.array.html


It's inferring it from the array literal in the first place, but it is a different kind of inference from the original one. But I think that's what the question was asking for:

> What's a sane/legit use case of such inferring in general?

Depends how you interpret "such inferring"


It's inferring the size of `doubled`


A few use cases:

    let foobar = Default::default();
    do_something_with(foobar); // where do_something_with takes a FooBar param
One I often use in unit tests:

    fn generate_ids<const N: usize>() -> [Id; N] { ... }

    let [id_button, id_label, id_thing, id_other_thing] = generate_ids();
(before const generics were stabilized, the above code instead had multiple utility functions, eg generate_2_ids, generate_3_ids, etc)


You general type inference, or inferring the array length? The latter follows from the former, so it would be kind of a weird hole in the language if type inference worked everywhere except array lengths.


"Scattered through multiple lines" inference :) Such code makes another programmer analyze multiple lines to deduce types in the head.


The tooling ecosystem, for instance rust-analyzer, make this not an issue if you're reading the code in an environment that supports it. I have rust-analyzer to display inferred types when I hold `control+command`.

In other languages, this can be a brittle/expensive operation due to meh tooling. But with Rust, it tends to "just work".

Online viewers of source e.g. Github and such don't have all this information exposed yet, but they're incrementally getting there. For instance, "go to definition" works for Rust code in Github.

One could imagine a generic file format included as an artifact in source control, that online viewers could consume, to annotate spans of source code. Go to definition, show expanded type, etc.


In practice, type inference works best when paired with editor integration. Most languages/editors will give you hover-overs with inferred types, but some like rust-analyzer have started going further (the greyed-out text here is all inlaid by the editor): https://user-images.githubusercontent.com/3971413/96668192-1...


nit: wouldn't it be usize since the only way to get T is from the bare numbers which default to usize?


"bare numbers" don't default to usize; integers default to i32 and floats to f64.


It does seem to be choosing [usize; 5] in this case though.

https://rust.godbolt.org/z/qasxnrTf9


Yes, that is true. If you use a literal array, you get i32 for the element type.

I believe this is because of the signature:

  pub fn from_fn<T, const N: usize, F>(cb: F) -> [T; N] 
  where
      F: FnMut(usize) -> T, 
While T is unconstrained, N is a usize, which is used in the closure, which we then re-use as the element.


OK this is just kinda funny - I was right, but only by accident. For some reason I thought that the literal defaulted to usize not i32. The fact that this path makes my assertion of usize correct is pretty cool though. Thanks for diving into it!


In this case the "bare numbers" do get inferred to be usize due to the signature of from_fn.


Yes, I realize that I may have read these words slightly differently than the parent meant them! I just said the same thing in a reply to your sibling.


That rocks


Yep, you could change the asserted length and it would succeed:

  assert_eq!(array, [0, 1, 2]);
If you tried both—

  assert_eq!(array, [0, 1, 2]);
  assert_eq!(array, [0, 1, 2, 3, 4]);
—the assertions would never run because it wouldn’t even compile:

  error[E0277]: can't compare `[usize; 3]` with `[{integer}; 5]`




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: