One of the reasons I‘m writing more and more Rust during my free time (despite really liking Go) is that the Rust compiler points out so many problems with my code that the Rust compiler, alone, is making me a better programmer in other languages. Problems which are applicable to C/C++/Go that I’ve either forgotten or have never even considered are pointed out by the Rust compiler.

A few days ago I wrote an enum for which I implemented a convenience method that returns a variant’s associated integer value. The integer value is to be used as an index into a Vec<&str>.

The incorrect version was written as follows.

#[derive(Debug, PartialEq)]

enum IndexEnum {

A = 0,

B,

C,

D

} impl IndexEnum { fn idx(&self) -> usize {

*self as usize

}

}

which led to the following compiler error.

Compiling rust_casting v0.1.0 (file:///Users/ereichert/dev/rust_casting)

src/main.rs:12:5: 12:10 error: cannot move out of borrowed content [E0507]

src/main.rs:12 *self as usize

^~~~~

error: aborting due to previous error

Could not compile `rust_casting`. To learn more, run the command again with — verbose.

I was confused by the attempt to move self. I did some experimenting and reading before coming to the following, working, version of the code. This version demonstrates why I was confused and it led to a discovery.

#[derive(Debug, PartialEq, Clone, Copy)]

enum IndexEnum {

A = 0,

B,

C,

D

} impl IndexEnum { fn idx(&self) -> usize {

let x = *self as usize;

return x; //return used for emphasis, it's not idiomatic, don't do that

}

}

The attempted move was caused by the missing Clone and Copy annotations on IndexEnum which, together, provide for copying memory automatically (without .clone()) instead of moving ownership.

But this version also exposed that the move was not happening on the return of the value but on the cast. That is, the move wasn’t happening at “return x;” it was happening at “*self as usize”.

Casts are moves or copies.

Either I never knew that or I forgot about it and it was the Rust compiler that pointed it out. I have since read casts are the same in C and C++. I haven’t checked Go but now that I understand why casts are copies or moves, I doubt Go is different.

I wrote a simple program to illustrate the copy.

fn main() {

let x: i32 = 5;



//This is a copy operation. That is, y is not bound to the same memory as x.

let y = x as i64; //This is also a copy operation even though y and z are of the same type.

let z = y as i64; println!(“memory address of x = {:p}, memory address of y = {:p}, memory address of z = {:p}”, &x, &y, &z);

}

If you run the program on the Rust playground you will get different memory addresses for each of the bindings. So even if the bindings are of the same type, as demonstrated by y and z, the cast will cause a copy.

memory address of x = 0x7fff8681bb84, memory address of y = 0x7fff8681bb78, memory address of z = 0x7fff8681bb70

Finally, after dealing with Scala for five years, I’m not a fan of a lot of implicit code. I’m also not a fan of using Copy unless it’s absolutely necessary. So the final (I think) version of the enum implementation actually wound up as follows.

#[derive(Debug, PartialEq, Clone)]

enum IndexEnum {

A = 0,

B,

C,

D

} impl IndexEnum { fn idx(&self) -> usize {

self.clone() as usize

}

}

It should be noted this version causes two copies. The first when self is cloned and the second when the usize value is returned. I could Box the usize value to avoid the second copy but that would be a very, very micro-optimization if it optimized anything at all. I suspect building a Box would negate the advantage of using it.

I’d love to know if there’s a better way to do this that avoids the second copy.