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.
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.
As someone who's currently working on some Python code, I heartily agree with this.
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
)
Hmmm, that sure ain't great. I see two solutions:
-
Have
Range
beCopy
, despite it being anIterator
: 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 notmut
errors). If they don't want that mutation, then using it in afor
loop willCopy
it anyways thanks toIntoIterator
being the identity function over aIterator
, and if they do intend to mutate the range while mutating, then they can use thewhile let Some(n) = range.next()
unsugared form. Really suprising that it is notCopy
to be honest. -
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.
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.
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)
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.
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();
}
Top:
runtime safety - low chances of things failing in production,
low overhead
transparency and exclusiveness in the community
Flop:
Refactor overhead