Rust - What would be your top and flop?

That's possibly something I'll have to take some time for, thanks.

To be honest I love my turbofish now that I know how it's used and how its syntax is.

Top: Rusts Ecosystem
Flop: We need more easy to find guides for the rust magic like OwningRef, which makes some stuff possible on threaded state, that you can't do otherwise. The first time it took me around an hour to it to run, with all the lifetimes 'n stuff? The same goes btw for futures, if you get to know how they work and some practice they're fairly easy, but before you'll have a really hard time.

1 Like

Top: If I had to pick one thing... right now, it would be cargo. Maybe that's because I've spent much of the last week fighting with CMake over in CPP land, but... cargo is just amazing.

Of course, there are many other things, also at the top of my list is rustdoc, and then of course to do with the language itself, probably my top thing would be the same as @musicmatze ... Some people who come from CPP (or elsewhere) think the borrow checker is annoying and limiting. IMO, it's completely freeing, allowing me to know that if I'm making a huge refactor or just going at a problem that I don't have any major unsoundness holes once it compiles. Amazing.

Flop: RLS/Editor integration in general. Look, RLS is an awesome effort and 80% of the time that I'm using it, it's quite good. However, the other 20% of the time it's either repeatedly crashing because things are too borked that it won't even begin to spit out errors, is unable to find a proper go to definition (my biggest repeated complaint), or some other quite obvious flaw.

5 Likes

As someone who's currently working on some Python code, I heartily agree with this.

1 Like

Care to elaborate your statement?

Sure: Range isn't Copy even when it's ranging over a Copy type because it's also an Iterator, and having an Iterator that's Copy leads to easy miss mistakes. However, the major use for range as an Iterator is something like

for i in 0 .. foo.len() {
  // whatever
}

So, in that position, as the target of a for loop, the Range has IntoIterator called on it anyway, because that's how for is specified to work. Thusly, there's no reason to have it be an Iterator itself directly.

Which is all a shame because now any data type that holds a Range can't derive Copy, even if it's a data type that has nothing to do with iteration at all.

Absolute flub.

(Edit: oh, yeah, and RangeInclusive is weird and it has an extra bool field internally, so 0..=5 is actually more bytes than 0..5)

7 Likes

Hmmm, that sure ain't great. I see two solutions:

  1. Have Range be Copy, despite it being an Iterator: if someone actually stores / holds a range instead of using it right away then surely they know that Iteration mutates the value (if they don't they will get plenty of is not mut errors). If they don't want that mutation, then using it in a for loop will Copy it anyways thanks to IntoIterator being the identity function over a Iterator, and if they do intend to mutate the range while mutating, then they can use the while let Some(n) = range.next() unsugared form. Really suprising that it is not Copy to be honest.

  2. Define your own trivial Range struct:

    use ::std::{fmt::Debug, iter::Step};
    
    /// constraint alias
    pub
    trait Idx : Copy + Debug + Eq + Step {}
    impl<T : Copy + Debug + Eq + Step> Idx for T {}
    
    // type level enum
    trait BoundType : Debug + Default + Copy + Eq {}
        impl BoundType for Exclusive {}
        #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
        pub
        struct Exclusive;
    
        impl BoundType for Inclusive {}
        #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
        pub
        struct Inclusive;
    
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub
    struct RangeCopy<T : Idx, B : BoundType>
    {
        pub
        start: T,
    
        pub
        end: T,
    
        _bound_type: B,
    }
    
    pub
    type RangeExclusiveCopy<T> = RangeCopy<T, Exclusive>;
    
    pub
    type RangeInclusiveCopy<T> = RangeCopy<T, Inclusive>;
    
    impl<T : Idx> RangeExclusiveCopy<T> {
        #[inline(always)]
        pub
        fn contains (self: Self, value: T) -> bool
        {
            self.start <= value && value < self.end
        }
    }
    
    impl<T : Idx> RangeInclusiveCopy<T> {
        #[inline(always)]
        pub
        fn contains (self: Self, value: T) -> bool
        {
            self.start <= value && value <= self.end
        }
    }
    
    impl<T : Idx> From<Range<T>> for RangeExclusiveCopy<T> {
        #[inline]
        fn from (range: Range<T>) -> Self
        {
            let Range { start, end } = range;
            RangeCopy { start, end, _bound_type: BoundType::default() }
        }
    }
    
    impl<T : Idx> From<RangeExclusiveCopy<T>> for Range<T> {
        #[inline]
        fn from (range: RangeExclusiveCopy<T>) -> Self
        {
            let RangeCopy { start, end, .. } = range;
            Self { start, end }
        }
    }
    
    impl<T : Idx> IntoIterator for RangeExclusiveCopy<T> {
        type Item = T;
        type IntoIter = Range<T>;
    
        #[inline]
        fn into_iter (self: Self) -> Self::IntoIter
        {
             self.into()
        }
    }
    
    // etc.
    

Would that be backward compatible? If yes, where could we open an issue to further discuss adding this bound?

Top: ecosystem+cargo+clippy+crates, etc.
Flop: Compilation time with respect to golang. I would love to see this getting improved.

There is an open issue about impl Copy for Range here. There is also this comment which explains the motivation for not implementing it.

2 Likes

Hmm, I really expected a deeper reason than the one provided there.The example would actually lint the unneeded mut! Maybe back in 2015 it looked dangerous, but nowadays Rust programmers should know the unsugaring of a for loop.

(Maybe a lint against using for x in iter sugar when iter is already an iterator instead of while let Some(x) = iter.next())

I feel like linting against iterators in for-loops would be counter-productive, I use them plenty of times. In any case, arguments about changing the traits that Range implements are probably off-topic for this thread and should probably go on to the linked open issue. :slightly_smiling_face:

1 Like

the unary negation prefix operator ! is not readable enough

This. A lifetime's habit has been to put a space after the bang, but unfortunately rustfmt disagrees (and no option to tell it otherwise). There is strong pressure to rustfmt everything at $WORK, and this remains the chief irritant for me. (I'm not a fan, but one must be pragmatic sometimes)

2 Likes

Looks like you hit the wrong reply button. @Yandros was the one who said that

1 Like

Top: A lot of things that are statements in other languages are expressions in Rust (if-else conditionals, explicit sub-scopes, etc).

Flop: Readability of code/discoverability of APIs is kind of a problem right now (especially in futures/tokio codebases), unless you are intimate with the types being used in the code-snippet. In a generics & trait heavy code snippet, it's very hard to reason where some methods (the actual type or some random trait imported into the scope) came from, what the useful type is that something has returned (in a futures heavy codebase I'm mostly interested in Future::Item & Future::Error resolved values, not the mega-type of all the combinators). Currently all of this is hard both for me as a developer and for the various code-completion plugins in IDEs/text editors. I'm certain that with time all of this will be solved in some way or another, but currently that's the biggest pain point for me.

1 Like

Top: useful error messages! I am glad to put blind faith in rustc's suggestions.

Flop: (very minor nit) the following syntax is not supported

if let Some(a) = maybe_errors() && a.foo() == 42 {
     bar();
}
1 Like

The relevant eRFC is already merged, so hopefully it will work in no so distant future.

4 Likes

Top:
runtime safety - low chances of things failing in production,
low overhead
transparency and exclusiveness in the community

Flop:
Refactor overhead