One of the rustlings exercises on primitive is about slices.
When I tried to solve this exercise I started by using the [start...end] syntax to take a slice into the given array as such
fn slice_out_of_array() {
let a: [u8; 5] = [1, 2, 3, 4, 5];
let nice_slice = a[1..4];
assert_eq!([2, 3, 4], nice_slice)
}
The compiler complains and tells me:
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
--> exercises/primitive_types/primitive_types4.rs:10:9
|
10 | let nice_slice = a[1..4];
| ^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `[u8]`
= note: all local variables must have a statically known size
= help: unsized locals are gated as an unstable feature
help: consider borrowing here
|
10 | let nice_slice = &a[1..4];
| +
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
running rustc --explain E0277 doesn't really answer my question. It explains what it means for an argument not to implement a trait when the function specifies it should. That is clear to me, but i am not completely clear on what is going wrong here.
This is what i think is happening, but I'd like to hear other's opinions.
The compiler can't figure out from the slice syntax how big the resulting slice will be and so it can't allocate the proper space on the stack.
By default slices are primitives and live on the stack instead of the heap.
If I take the suggestion and add & the compiler pushes on the stack a reference -- which has a known size -- to the original array already on the stack. This solves the problem.
Is this correct? Is there a case where i can take a slice and not add the & symbol?
The compiler can't figure out from the slice syntax how big the resulting slice will be and so it can't allocate the proper space on the stack.
This is true. We say that slices are Dynamically Sized Types (DSTs), or that they are not Sized, or that they do not implement the Sized trait (this is the reason rustc gives you E0277, type does not implement trait).
By default slices are primitives and live on the stack instead of the heap.
This is not precise: slices are primitives, but Rust primitives, like any other object, can also live on the heap. It is just that you are trying to move it, so the compiler needs to reserve space for it on the stack.
If I take the suggestion and add & the compiler pushes on the stack a reference -- which has a known size -- to the original array already on the stack. This solves the problem.
Yes. Essentially, the compiler just offsets the address and shorten the length. A reference to slice is a pair of (address, length).
Is there a case where i can take a slice and not add the & symbol?
Yes and no.
No, because you always take a reference of some kind. All DSTs can only come behind an indirection (a reference, a Box, a Rc...).
But also yes, because you does not always need to add the & symbol. Sometimes the compiler is doing it for you: when you call a method on the slice, thanks to autoref:
fn slice_out_of_array() {
let a: [u8; 5] = [1, 2, 3, 4, 5];
a[1..4].is_empty(); // Just an example. Equivalent to:
(&a[1..4]).is_empty();
}
You can also moves it to the heap (but this also takes a reference under the hood):
fn slice_out_of_array() {
let a: [u8; 5] = [1, 2, 3, 4, 5];
let b: Box<[u8]> = a[1..4].into();
}
Related
Forgive me if there is an obvious answer to the question I'm asking, but I just don't quite understand it.
The Dynamically Sized Types and the Sized Trait section in chapter 19.3 Advanced Types of the 《The Rust Programming Language》 mentions:
Rust needs to know how much memory to allocate for any value of a particular type, and all values of a type must use the same amount of memory. If Rust allowed us to write this code, these two str values would need to take up the same amount of space. But they have different lengths: s1 needs 12 bytes of storage and s2 needs 15. This is why it’s not possible to create a variable holding a dynamically sized type.
When it says "and all values of a type must use the same amount of memory", it is meant to refer to dynamically sized types, not types such as vectors or arrays, right? v1 and v2 are also unlikely to occupy the same amount of memory.
let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3, 4, 5, 6];
It's correct and considers vectors as well. A Vec<T> is roughly just a pointer to a position on the heap, a capacity, and a length. It could be defined, more or less, as
pub struct Vec<T>(T*, usize, usize);
And every value of that structure clearly has the same size. When Rust says that every value of a type has to have the same size, it only refers to the size of the structure itself, not to the recursive size of all things it points to. Box<T> has a constant size, regardless of T, which is why Box can hold even things that are dynamically sized, such as trait objects. Likewise, String is basically just a pointer.
Likewise, if we define
pub enum MyEnum {
A(i32),
B(i32, i32),
}
Then MyEnum::A is no smaller than MyEnum::B, for similar reasons, despite the latter having more data than the former.
Every type that can be stored and accessed without the indirection of a reference or Box must have the Sized trait implemented. This means that every instance of the type will have the same size. A str is a DST, as the data it holds can be of a variable length, and thus, you can only access strs as references, or from String, which holds the str data on the heap, through a pointer.
Every Vec also takes the same space, which is 24 bytes on a 64-bit machine.
For example:
let vec = vec![1, 2, 3, 4];
println!("{}", std::mem::size_of_val(&vec)); // Prints '24'.
I ran the following example in the standard library documentation, and there was a puzzle.
I found an implementation of the BorrowMut trait with Vec,
I don't understand how it works. For example, where the code below indicates that No.1 works, why doesn't No.2 work, what does the generic T do?
use std::borrow::BorrowMut;
fn check<T: BorrowMut<[i32]>>(mut v: T) {
assert_eq!(&mut [1, 2, 3], v.borrow_mut()); // ! no.1 can call, Why?
}
fn main() {
let v = vec![1, 2, 3];
// v.borrow_mut(); // ! no.2 Can't call,Why?
check(v);
}
The full error shown by rustc explains it pretty well. With emphasis added:
error[E0283]: type annotations needed
--> src/main.rs:9:7
|
9 | v.borrow_mut(); //! no.2 Can't call,Why?
| --^^^^^^^^^^--
| | |
| | cannot infer type for type parameter `Borrowed` declared on the trait `BorrowMut`
| this method call resolves to `&mut Borrowed`
| help: use the fully qualified path for the potential candidate: `<Vec<T> as BorrowMut<[T]>>::borrow_mut(v)`
|
= note: cannot satisfy `Vec<i32>: BorrowMut<_>`
BorrowMut can be implemented multiple times on a type. For example, both BorrowMut<Foo> and BorrowMut<Bar> could be implemented for Vec<i32>; in those examples, Foo and Bar take the place of the "type parameter Borrowed declared on the trait BorrowMut", as shown in its documentation (click "Show declaration").
In "no.1" you have specified that T: BorrowMut<[i32]>; since no other implementations of BorrowMut for T have been mentioned, the "type parameter Borrowed declared on the trait BorrowMut" can unambiguously be inferred to be [i32].
In "no.2", there is ambiguity over which implementation of BorrowMut you're after: even if no other implementations of BorrowMut for Vec<i32> are in scope right now, they could be lurking somewhere the compiler doesn't know about (pending being brought in-scope with a use statement); and even if no others exist right now, an upstream crate (namely, the std library) could add one in the future—and that would then break your code. Therefore the compiler asks you to remove the ambiguity by explicitly informing it which implementation you're after. It does this by reporting "type annotations needed", and even shows you how: "use the fully qualified path for the potential candidate: <Vec<T> as BorrowMut<[T]>>::borrow_mut(v)". We can do that here:
<Vec<i32> as BorrowMut<[i32]>>::borrow_mut(v)
However this won't work for a separate reason: Rust only performs deref coercion when calling with the . method syntax—when calling like this instead, you'd have to explicitly pass &mut v instead of v (I've filed an issue about this erroneous suggestion); and that still won't work in your case because v was not declared mutable.
The compiler also concluded with:
For more information about an error, try `rustc --explain E0283`.
Doing that would have displayed this extra information, which may also have helped.
Do please suggest how the error message could have explained the problem more clearly.
In actual fact, the compiler's suggestion above is more verbose than it requires to resolve the ambiguity in this case. You could also have resolved it without mentioning the type of v in either of these ways:
<dyn BorrowMut<[i32]>>::borrow_mut(&mut v)
BorrowMut::<[i32]>::borrow_mut(&mut v)
Also note that, in the case of Vec, you can obtain a mutable slice of its content (which is what this implementation of BorrowMut provides for you) via its intrinsic as_mut_slice method, indexing (via its implementation of std::ops::IndexMut), or calling upon std::ops::DerefMut::deref_mut whether explicitly or implicitly (i.e. using the dereference operator). Respectively:
v.as_mut_slice()
&mut v[..]
v.deref_mut() // DerefMut must be in scope, else qualify as above
&mut *v
Indeed, because of the dereferencing, &mut Vec also coerces to a mutable slice at coercion sites such as function arguments and let bindings—so you can often just use &mut v and Rust will do the rest for you.
See all approaches discussed in this answer on the Rust Playground.
Given that they all compile down to exactly the same code, which you use is really just a matter of preference. But, in order to keep the code as clear (and therefore maintainable) as possible, I would suggest that the indexing approach &mut v[..] most clearly and concisely indicates that you're taking a mutable slice over the whole of the vector.
I have a weird looking piece of code and I understand that Rust compiler rejects it, but I don't understand the specific error message.
TL;DR; Why does Rust rejects this with "doesn't have a size known at compile-time" instead of something like "illegal syntax" or "can't assign a slice to a slice"?
fn main() {
let mut data1 = vec![0, 1, 2, 3].as_slice();
let mut data2 = vec![8, 9].as_slice();
data1[1..3] = *data2; // of course this is illegal; but I don't understand the error message
}
This is the code. In theory it should replace a sub slice of data1 with the data in slice data2. (The proper way would be a for loop for example, I know!). But let's have a look at this. Rust compiler says:
error[E0277]: the size for values of type `[{integer}]` cannot be known at compilation time
--> src\main.rs:4:5
|
4 | data1[1..3] = *data2;
| ^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `[{integer}]`
Why is the error at data1[1..3], only on the left hand side of the assignment? I expected that Rust compiler tells the error is on the right side of the assignment or even the whole assigment. Something like "can't assign a slice to a slice".
But why is Rust telling exactly this message? Why is data1[1..3] of unknown size in this case? Of course [{integer}] is not Sized. But there should be no stack allocation necessary at this point? I'd expect any other error message.
I can't see a slice on the left hand side of your assignment and neither can the compiler!
Always try to reduce your example as much as possible, most of the time doing so you would find what the compiler is actually complaining about. So, if you would try and write this:
let data1 = [0u8, 1, 2, 3];
let x = data1[1..3];
You would see, what the compiler is actually complaining about in your example:
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
--> src/main.rs:4:9
|
4 | let x = data1[1..3];
| ^ ----------- help: consider borrowing here: `&data1[1..3]`
| |
| doesn't have a size known at compile-time
You see, there's a huge difference between [T] and &[T]! [T] is a contiguous sequence of Ts, while &[T] is a dynamically-sized view into this contiguous sequence. The former has no statically known size while the latter does.
And before you say that you used the Vec::as_slice method, after that you tried to take a slice of a slice, that is:
// Type of `data1` is `&[u8]`
let data1 = vec![0u8, 1, 2, 3].as_slice();
// Type of `x` is `[u8]`
// (which doesn't have a size known at compile-time
let x = data1[1..3];
So I believe the answer to your question is that the compiler didn't get to the point where it can actually look at the other side of the assignment, because while it tried to figure out the left hand side it already found a problem: an expression that has no known size at compile time.
Now, if you would actually write a slice on the left hand side:
let mut data1 = [0u8, 1, 2, 3];
let data2 = [8u8, 9];
&mut data1[1..3] = &data2[..];
Then the compiler would complain about the invalid nature of the left hand side (among other things):
error[E0070]: invalid left-hand side of assignment
--> src/main.rs:6:22
|
6 | &mut data1[1..3] = &data2[..];
| ---------------- ^
| |
| cannot assign to this expression
Consider this example:
fn main() {
let v: Vec<i32> = vec![1, 2, 3, 4, 5];
let b: i32 = (&v[2]) * 4.0;
println!("product of third value with 4 is {}", b);
}
This fails as expected as float can't be multiplied with &i32.
error[E0277]: cannot multiply `{float}` to `&i32`
--> src\main.rs:3:23
|
3 | let b: i32 = (&v[2]) * 4.0;
| ^ no implementation for `&i32 * {float}`
|
= help: the trait `std::ops::Mul<{float}>` is not implemented for `&i32`
But when I change the float to int, it works fine.
fn main() {
let v: Vec<i32> = vec![1, 2, 3, 4, 5];
let b: i32 = (&v[2]) * 4;
println!("product of third value with 4 is {}", b);
}
Did the compiler implement the operation between &i32 and i32?
If yes, how is this operation justified in such a type safe language?
Did the compiler implement the operation between &i32 and i32?
Yes. Well, not the compiler, but rather the standard library. You can see the impl in the documentation.
If yes, how is this operation justified in such a type safe language?
"Type safe" is not a Boolean property, but rather a spectrum. Most C++ programmers would say that C++ is type safe. Yet, C++ has many features that automatically cast between types (constructors, operator T, taking references of values, ...). When designing a programming language, one has to balance the risk of bugs (when introducing convenient type conversions) with the inconvenience (when not having them).
As an extreme example: consider if Option<T> would deref to T and panic if it was None. That's the behavior of most languages that have null. I think it's pretty clear that this "feature" has led to numerous bugs in the real world (search term "billion dollar mistake"). On the other hand, let's consider what bugs could be caused by having &i32 * i32 compile. I honestly can't think of any. Maaaybe someone wanted to multiply the raw pointer of one value with an integer? Rather unlikely in Rust. So since the chance of introducing bugs with this feature is very low, but it is convenient, it was decided to be implemented.
This is always something the designers have to balance. Different languages are in a different position on this spectrum. Rust would likely be considered "more typesafe" than C++, but not doubt, there are even "more typesafe" languages than Rust out there. In this context, "more typesafe" just meant: decisions leaned more towards "inconvenience instead of potential bugs".
I think you may be confusing &i32 from rust with &var from C.
In C,
int var = 5;
int newvar = &var * 4; /* this would be bad code,
should not multiply an address by an integer!
Of course, C will let you. */
the '&' operator returns the address of the variable 'var'.
However, in rust, the '&' operator borrows use of the variable var.
In Rust,
let var: i32 = 5;
assert_eq!(&var * 8, 40);
This works, because &var refers to 5, not to the address of var. Note that in C, the & is an operator. In Rust, the & is acting as part of the type of the variable. Hence, the type is &i32.
This is very confusing. If there were more characters left on standard keyboard, i am sure the designers would have used a different one.
Please see the book and carefully follow the diagrams. The examples in the book use String, which is allocated on the heap. Primitives, like i32 are normally allocated on the stack and may be completely optimized away by the compiler. Also, primitives are frequently copied even when reference notation is used, so that gets confusing. Still, I think it is easier to look at the heap examples using String first and then to consider how this would apply to primitives. The logic is the same, but the actual storage and optimization my be different.
It’s very simple actually: Rust will automatically dereference references for you. It’s not like C where you have to dereference a pointer yourself. Rust references are very similar to C++ references in this regard.
If I try this:
let vector = vec![1, 2, 3];
let slice = vector[1..2];
I get a compiler error:
error[E0277]: the trait bound `[{integer}]: std::marker::Sized` is not satisfied
--> src/main.rs:3:9
|
3 | let slice = vector[1..2];
| ^^^^^ ------------ help: consider borrowing here: `&vector[1..2]`
| |
| `[{integer}]` does not have a constant size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `[{integer}]`
= note: all local variables must have a statically known size
I get that we need let slice = &vector[1..2] as the compiler kindly suggests. This makes sense: a slice always comes from another value, thus you need to borrow the vector in this example.
However, I tried this:
let vector = vec![1, 2, 3];
let borrowed_vector = &vector;
let slice = borrowed_vector[1..2];
and I am back to the same error.
I ask because it seems a bit weird to require the &vector[] syntax if in fact borrowing (&) is always required.
A non-borrowed slice ([T]) exists, but you cannot use it as value as-is. It is only useful in other types where it is behind a pointer of some kind. For example (not exhaustive):
In borrowed slices: &[T]
In boxed slices (i.e. owned slices): Box<[T]>
In ref-counted slices: Rc<[T]>
That is because the compiler cannot know what the size of an unsized type on the stack but a pointer has a known size.
The type can also be used as part of a trait bound for static polymorphism: U: AsRef<[T]>.
It seems a bit weird to require this syntax &vector[] if in fact borrowing (&) is always required.
It is not always required to use an & with the indexing syntax because sometimes the referenced value can be dereferenced:
let scores = vec![1, 2, 3];
let score = scores[0];
See also:
What is the return type of the indexing operation?