Cursed Rust

Rust is a language with a lot of features. Sometimes those features have rough edges. Sometimes those rough edges are funny. Let's look at some.

Copy and Clone can diverge

#[derive(Debug)]
struct OhNo(u32);

impl Clone for OhNo {
    fn clone(&self) -> Self {
        OhNo(self.0 + 1)
    }
}

impl Copy for OhNo { }

fn main() {
    let oh = OhNo(3);
    dbg!(oh.clone());
    dbg!(oh);
}

Even worse, according to the docs, this isn't even a logic error, unlike inconsistent implementations of PartialOrd and Ord

Really long place expression

a place expression is an expression you can take the address of.

turns out if statements are place expressions. this is so obscure that not even the reference knows this, despite it being true since rust 1.0

fn main() {
    let arr = [10, 20];
    let arr_ref = &if true {
        arr[0]
    } else {
        arr[1]
    };
    dbg!(arr_ref)
}

krate vs crate_

t-compiler says to use krate, t-style says to use crate_

sidenote: how many of you have actually read the style guide? how many actually know that it exists?

Rust has reference variables! kinda..

use std::cell::Cell;

fn main() {
    let x = Cell::new(1);
    let ref y = x;
    x.set(2);
    let ref z = x;
    assert_eq!(y, z);
}

This is mostly just silly.

&* is actually useful

previous entries are here because they are weird and obscure. &* is here because it is actually useful and meaningful, despite the fact that it would be a very silly no-op in most languages.

Addendum: it's not just if

A reader asked me if a loop + break could also be a place expression. I thought do myself “well obviously that won't work”, before testing it out and realizing in horror that it does:

fn main() {
    let arr = [10, 20];
    let arr_ref = &loop { break arr[0]; };
    dbg!(arr_ref);
}

now this is weird.

Errata 2024-10-08: temporary lifetime extension is complicated

if is not a place expression. I was under the impression that the previous examples would not work unless it was, but actually my understanding of temporary lifetime extension is simply incomplete.

Addendum 2024-11-05: + is overloaded on strings

rust typically avoids C++ style operator overloading, favoring the haskell style of always having operators represent the same semantic operation.

nonetheless, you can use + for string concatenation, and you can use += as an alternative to push_str.

Addendum 2024-11-08: Calling methods of unnameable traits

Ever wanted an api that's impossible to use without glob imports? well now you can!

mod greet_ext {
    mod inner {
        pub trait GreetExt {
            fn greet(&self);
        }
        
        impl<T> GreetExt for T {
            fn greet(&self) {
                println!("hello, world!");
            }
        }
    }
    pub use inner::GreetExt as _;
}

pub use greet_ext::*;

fn main() {
    1.greet();
}

#rust #programming