Blog: Rustic Bits


#1

Wherein we look at the small things that make for rustic code.


#2

Consider using an enum MyEnum { SomeState, OtherState } instead of bool for distinguishing two states when used in function arguments to clarify intent.

Is using a bool newtype considered a bad idea?

pub struct SomeState(pub bool);
impl Deref for SomeState { type Target = bool; ... }

if *state { ... }

vs

pub enum SomeState { Yes, No }
if state == SomeState::Yes { ... }
// or
pub enum SomeState { SomeState, NotSomeState }
if state == SomeState::SomeState { ... }

#3

Reverse dependencies link is broken


#4

@tshepang I shall investigate. Thank you. Edit: Fixed.

@gkoz depends. If your type is literally either true or false, it’s ok. But often those values come to represent states that can be named more apropriately.


#5

I recently had the situation of having to return the state of a traffic light (only red or green) and even if it can be translated as “am I allowed to drive” I preferred the enum to make everything more self-explanatory:

#[derive(Copy,Clone,PartialEq,Eq)]
enum Color
{
    RED,
    GREEN,
}

fn light(time: usize) -> Color
{
    if /* something more complicated */ time < 10
    {
        Color::RED
    }
    else
    {
       Color::GREEN
    }
}

fn main()
{
    let time = 5;
    // very explicit, makes more sense when reading than an asteriks
    if light(time) == Color::GREEN
    {
        /* do something */
    }
}

Also, please excuse my not very rust-like formatting and the bad naming. Latter was being changed to reduce complexity.


#7

Consider using an enum MyEnum { SomeState, OtherState } instead of bool for distinguishing two states when used in function arguments to clarify intent.

This avoids the “boolean blindness”:
https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

You can also think about a shorter in-place way to define similar boolean enums:

fn foo(color: enum { RED, GREEN }) {}

You can it with:

foo(RED);

While some other languages regard shadowing as something evil,

It is often evil. I am still unsure Rust got this design decision right. It’s handy, but it’s strange to see it in such a safe language.

Resist the urge to .clone() to appease the borrow checker.

But sometimes you don’t want to waste too much time fixing the problem without .clone(), and in several situations the maximum performance is not necessary (so also using a Rc<> could be OK).

(I’d like this forum to have an icon for “hide this post for now while I fix it”.)


#8

It is subjective, but I usually get more bugs when I try to avoid shadowing. It usually happens like this:

  1. I have a nicely named foo.
  2. I make a modification to foo and call it (not so nicely) foo_some_stuff because the lint for the language I am using is angry.
  3. I accidentally use foo instead of foo_some_stuff because they have similar names and similar purpose and in general refer to the same thing in my head.

I really like the advice to “minimize visibility”, and shadowing is precisely about restricting visibility.


#9

The are also the bugs opposite of that you say: using foo believing you are using another foo (the shadowed one). So I guess in the end what matters is which the two design choices leads to less bugs :slight_smile:

By “instinct” I’d like Rust to be designed the opposite way, that is to forbid shadowing even of names from outer scopes :slight_smile: So all names are distinct. I have had a significant amount of bugs in C/C++/D code caused by shadowing of global variables, module-level variables, or variables of outer scopes (for nested functions). And I’ve even invented a way to manage such shadowing in a really precise way…

On the other hand I have less than three months of Rust experience, and so far I’ve written only small Rust programs, that’s why I have said “I am still unsure”, I will keep writing Rust code and eventually I’ll decide.


#10

You can forbid shadowing using clippy. Just #[deny(shadow_same, shadow_reuse, shadow_unrelated)].

I think shadowing with reusing the original value is mostly OK – for example let x = x + 1 in a recursive function to increment the recursion count, or let mut x = x to make something mutable within a scope only. Or let out = out.into_inner() to strip the buffer off a BufWriter.

Shadowing with something completely unrelated can lead to bugs however. In the end it boils down to a tradeoff, as so many things in programming.