8

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?

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
manikawnth
  • 2,739
  • 1
  • 25
  • 39

3 Answers3

13

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".

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
3

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.

Gardener
  • 2,591
  • 1
  • 13
  • 22
  • C will surely *not* let you multiply a pointer by an integer. – rodrigo Aug 31 '19 at 17:02
  • Yeah, GCC throws an error for that code unless I add a cast: `int var = 5; int newvar = (int) &var * 4;`. – cyclaminist Sep 01 '19 at 01:51
  • Your point is well taken. However, the example is a good one to illustrate the fact that `&` has a completely different meaning in the two languages. The fact that the Rust example does compile is quite instructive -- that &i32 and i32 can be multiplied againist each other shows that they both resolve to i32 operands and tha the `&` is part of the type definition, rather than a modifier that looks at a different location in memory ( in the case of C). – Gardener Mar 28 '23 at 06:31
-1

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.

MrMobster
  • 1,851
  • 16
  • 25
  • 3
    This is not universally true. In some situations, references are in fact automatically dereferenced; in some others it's not done by the language but by a library feature (like an impl for reference types); in yet other cases, references are not automatically dereferenced. – Lukas Kalbertodt Aug 31 '19 at 16:44