What does this line `[(queue_family, 0.5)].iter().cloned()` means

Hi,
I'm reading codes in graphics and when comes to the concept "queue family", I found codes like this:

[(queue_family, 0.5)].iter().cloned()

I'v never seen thing like this before, what does it mean? Especially the "0.5"?

EDITED. f32 -> f64 ( @godmar ), slice -> array ( @steffahn ); see discussions below

(queue_family, 0.5) is a tuple of type (Q, f64) assuming queue_family has type Q

[(queue_family, 0.5)] is an array containing 1 element, the tuple

[(queue_family, 0.5)].iter() is an iterator where items has type &(Q, f64)

[(queue_family, 0.5)].iter().cloned is an iterator where items have type (Q, f64)

2 Likes

I'm not sure what you are asking about "0.5" here — I assume you recognize that it's the fractional (floating-point) number one half. What it means specifically depends on the code using it, but you didn't provide enough context for us to be able to tell.

1 Like

Looks like a weird way of writing something effectively equivalent to

std::iter::once((queue_family.clone(), 0.5))

(âź¶ docs for iter::once; note that the call to clone() might be redundant, depending on the type and further use of the variable queue_family)

except that the original code could be used in a context where there are other values of type iter::Cloned<slice::Iter<'_, (Q, f64)>> that are required to have the same type; only in that case I wouldn’t suggest replacing it with iter::once.


In any case, it’s an iterator with a single item. That single item is a pair (i.e. tuple of 2 elements), the first element of the pair is a copy (or a “clone”) of the value queue_family, the second item is the floating point number 0.5.

I assume that should be f64 instead of f32?

1 Like

My bad, The code is a little weird to me so that I didn't recognized it's a tuple inside, and take the "0.5" as some grammar to slice by mistake.

Additionally, [(queue_family, 0.5)] is an array, not a slice.

I just checked ... and you're right. I can't believe floats defaulted to f64 all this time.

I'm not confident in my definition of array vs slice. I tried googling, and what I got so far is:

  1. arrays have fixed size @ compile time
  2. slices are array/vec-like things w/o fixed size @ compile time, that we can only refer to by reference

Is this the core difference, or is there something else ?

Yes, this is the core difference.

  • [T] has unknown length and therefore unknown size.
  • [T; N] has known length and therefore known size.

Since we usually can't own things which have unknown size, we refer to &[T] as a slice because that's the most practical way to reason about them.

1 Like

Sounds okay for an intuitive understanding. I was just talking about types though. In Rust, there are two similar-looking types, one is [T; N], which is called “array”, and the other is [T], which is an unsized type and called “slice”. Actually, there’s two different usages of the name “slice”, either referring to the unized type or to a reference to it.

E.g. while I’m writing this @OptimisticPeach gave a similar answer, and they did use the term to mean a reference, i.e. &[T]. The standard library calls [T] “slice” (as linked above).

In the original code in question, [(queue_family, 0.5)].iter().cloned(), we see a call to the method iter. This method is a method on slices, but array can be converted to slices (when behind a reference, a process called an “unsize coercion”, similar to coercions from some type &T to &dyn SomeTrait trait objects. Furthermore, unsize coercions play a role in method resolution, which is why .iter() can be called on an array. So in effect,

[(queue_family, 0.5)].iter()

desugars to something like

(&[(queue_family, 0.5)] as &[_]).iter()

where

[(queue_family, 0.5)]: [(Q, f64); 1]

&[(queue_family, 0.5)]: &[(Q, f64); 1]

(&[(queue_family, 0.5)] as &[_]): &[(Q, f64)]

in other words, the method resolution implicitly adds a reference as well as a coercion from array to slice.


Going back to the things you said

that’s true, and the size appears in the type; for [T; N] the N is the size.

and in fact, a slice can be part of / pointing into an array or vec. This is demonstrated by the fact that both &[T; N] and &Vec<T> can be coerced to &[T]. Similar to how trait objects can abstract over multiple types, slices do the same in that they “unify” multiple types.

Yes, which you can see by the fact that the type [T] doesn’t advertise its size (syntactically the only difference from the type of an array). Note that e.g. Vec doesn’t have a fixed size either, so this isn’t a property uniquely characterizing slices.

This is more generally the case for all unsized types. Or “dynamically sized” types. Whatever you like to call them. Sometimes abbreviated DSTs.


Note that “by reference” includes all kind of forms of indirections. There’s the classic &[T] and similarly &mut [T], but also Box<[T]> and Rc<[T]> or Arc<[T]>

2 Likes

The key points I got are:
T = some type, N = some integer

  1. [T; N] = fixed size, array
  2. &[T; N] = ref to array
  3. [T] = unsized, slice
  4. &[T] = ref to slice, also referred to as 'slice'
  5. For all N, T, Rust does auto conversions of &[T; N] into &[T]

Did I miss anything ?

Well, if we're being pedantic, T is Sized and N >= 0.

But given those are implicit, I'd say that that's the major distinction between the bunch.

1 Like

Seems decent. There’s no right or wrong in how many notable points you take away; anyway, I don’t think there was anything too important your didn’t list. Note there’s also “string slices”, i.e. the type str (the same kind of naming issue applies whether str or &str is a string slice). The interaction between Vec<u8> and &[u8] is analogous to String vs &str (e.g. including conversion from &String to &str).

Note that for string slices, there’s no equivalent of an array. Also, slices cannot really change their length, i. e. you cannot resize a &mut [T], only mutate every element. (Actually, you can shorten a slice, but that operation just narrows your view into the memory, and the elements you aren’t looking at anymore are still there; calling this resizing is a bit like pretending the room goes dark when you close your eyes.) Since strings in Rust are UTF-8 encoded which is a variable-length encoding, you can do surprisingly little with a &mut str, especially if you only want to use standard-library methods. (make_ascii_uppercase and make_ascii_lowercase are str’s only &mut self methods that actually do anything; ASCII-based mutation works because all ascii characters are encoded with the same length (1 byte))


Ah, while we’re at it, maybe I should mention the most important feature of slices in general. You can slice them up, i.e. split them into parts, narrow them down to a shorter subslice, etc. Using index notation. If you have an array or vector v, then &v[3..6] for example is a slice containing 3 elements, the elements of v at index 3, 4, and 5. Using a full-range, writing &v[..] is another explicit way of getting a slice of the whole thing. On that note, an alternative desugaring of [(queue_family, 0.5)].iter() is (&[(queue_family, 0.5)][..]).iter(), where

[(queue_family, 0.5)]: [(Q, f64); 1]

[(queue_family, 0.5)][..]: [(Q, f64)]

&[(queue_family, 0.5)][..]: &[(Q, f64)]

While the type-checker will recognize [(queue_family, 0.5)][..] as an expression of type [(Q, f64)], don’t try to evaluate it. It’s a “place expression” only, you cannot move a slice by-value. But using it as a sub-expression in &[(queue_family, 0.5)][..], creating a reference to that “place” works fine.


BTW, the book has a chapter on slices (mostly focusing on string slices)

The Slice Type - The Rust Programming Language

2 Likes

Sorry if the last message came off as curt. I appreciate all your detailed answers; I was trying to build a mental model of: "If I was acting as the rust compiler, what is the minimal set of axioms I need to sort out thees two cases."

1 Like

Right; in this case

is not really the axiom. Instead [T; N] converting to [T] behind indirection (unsize coercion; the same mechanism as for turning things into trait objects) is the axiom. In terms of (unstable) standard library traits, the compiler recognizes that [T; N]: Unsize<[T]> (by the way, this is mentioned in the linked docs of Unsize). Together with this CoerceedUnsize impl, you get your conversion. You can also find the whole in the reference:

Unsized Coercions in Type Coercions – The Rust Reference

1 Like

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.