29 ms·
My least favorite Rust type
- CJefferson 6y agoThis was an enjoyable read, and also did an excellent job of explaining some issues I have frequently had work ranges, particularly having to borrow integers, which always feels awkward.
- j4nt4b 6y agoI'm only just learning Rust so I don't know much about Rust development, but what are the chances that any of the suggestions listed at the end make it into the language?
- JoshTriplett 6y agoI'd love to see RangeInclusive fixed in the manner described. Part of what's blocking such a fix is that we have hard backwards compatibility guarantees. We'd have to do some kind of edition-based transition, where ranges start desugaring to a different type in a new edition than they did in the previous edition. We could do that, and there are various discussions going on to consider such approaches, but we're somewhat hesitant to go down that path.
- cryptonector 6y agoThen again, there's no Rust ABI...
- kibwen 6y agoThe lack of a stable Rust ABI doesn't have any relation to the ability to evolve the Range type.
- kibwen 6y agoThe problems with Range are well-understood by now, so I don't think that it would be too much of a stretch to argue that a brand-new type could be added to the stdlib with some variation on the semantics given at the end of the post. However, transitioning the dedicated range syntax to that new type would be the tricky part.
- theon144 6y agoDeprecate two-dot syntax, introduce three-dot syntax? /s
- estebank 6y agoFunnily enough, there was a ... syntax for inclusive ranges, but because when looking at code at a glance it is hard to distinguish between .. and ..., it was replaced with the current syntax: ..=
- Dominear 6y agohave you seen this yet https://www.youtube.com/watch?v=xVGO0NIO848 https://www.youtube.com/watch?v=xVGO0NIO848
- IshKebab 6y agoPretty much zero. All his points are 100% valid, but they're probably annoyances that we can live with, and Range is a really core type - changing it would break basically all Rust code in existence.
- devit 6y agoNew types could be introduced, a..b and other range syntax could resolve to them in next edition and cargo fix could add ".into()" to convert when needed.
- adamnemecek 6y agoI wouldn't call it a wart, these things have been debated pretty extensively.
- hobofan 6y agoJust because it has been debated extensively doesn't mean it's not a wart. Having run into the "reverse range does not contain what you obviously expect it to contain" before and wasting a few hours on it, like many other people have and will continue to do in the future, definitely makes me want to call it a wart.
- adamnemecek 6y agoRust tries to be conservative with its semantics. You can always create your own range type and implement a deref/from for it to convert it to Rust range.
- swsieber 6y agoA well known wart that can be worked around is still a wart.
- CydeWeys 6y agoIt seems like in this case Rust actually failed to act conservatively in its semantics, by allowing ranges on overly generic types in a way that doesn't make sense.
- hobofan 6y agoI think the problem here is more that the semantics are documented in the "documentation", but are contrary to the intuition derived from the semantics in the type system. `.contains()` is only implemented for `Range<Idx: PartialOrd>`, which to me implies that when checking whether a value is contained in the Range, it has enough knowledge about the ordering of numbers that it should be able to still do a bounds check on reversely ordered numbers.
- cryptonector 6y ago
- WhyNott 6y agoI feel like this is an example of what Jonathan Blow calls a "Big Idea" or a "100% solution". His thesis is that when you make a feature of a language too abstract and usable in many different contexts, eventually there will be so many corner cases that the result will almost certainly be clunky and full of footguns. He claims that language designers should aim for "80% solutions" instead, which cover most common usages but limit themselves enough to avoid complexity. This runs in contrast to a lot of commonly accepted language design wisdom.
- skocznymroczny 6y agoReminds me of D at times. It has many fancy features and powerful metaprogramming. But it also comes with drawbacks, many simple language improvement proposals are being shot down because they break in presence of some advanced usage of those features.
- dathinab 6y agoBut that kinda isn't the case here. `Range` was discussed a lot before being stabilized and it's drawbacks where well known when it was stabilized. The reason it was stabilized that way anyway was because it happens to work out best for the most common use-cases. It's more of a "practically use-full but theoretically imperfect compromiss" thing. The main usage of range are: 1. To iterate over it 2. To slice things using it Which is what it is focused on in it's design. Sure ranges could be `Copy` but one of their main purposes is to be an iterator so it's reasonable to not make them copy as that would be a usability nightmare. Sure it's strange that you can construct a invalid range and then panic when you use it to slice something. But the alternative would be to make the creation of range fallible which is a usability nightmare. Furthermore validity depends on what you use it one, so a backwards range might be a very reasonable thing for some use-cases so practically it's best to make every `Range` valid, but not necessary every usage of one. Sure exclusive ranges based on start+end can't contain the maximal value but that's a fundamental property of exclusive ranges defined through start+end. There is a reason mathematics have 4 kind's of ranges (differing in exclusiveness in start/stop). Sure `.contains` takes a reference, but that's a problem about how rust can't specialize traits in how they need references for Copy methods. Not having that would prevent the usage of ranges of `BigNums` or similar reasonable usages. Sure `RangeInclusive` could be made shorter but that also means you can't have empty inclusive ranges and you can't use it directly as an iterator, which is probably the most common use case of inclusive ranges. All in all the `Range` types are a compromise optimized for it's most common use cases. That makes some parts sub-optimal if used for other cases but you can also always use your own types so that's not really a problem in practice. Also he does some mistakes: - You can't enforce `start <= end` at construction time without making the constructor fallible which would be a ergonomic nightmare. Which means that neither `[T]::get()` or `Range.len()` would get faster nor would is_empty get easier. The last point also sadly means that for certain arithmetic high performance tasks it can make sense to not use the rust provided range type but a custom one.
- tgb 6y agoWhy does Rust allow Ranges for types that don't have comparators in the first place?
- adamnemecek 6y agoMost of the methods are constrained to types that implement PartialOrd.
- saagarjha 6y agoWhat happens if I make a range bounded on one end by NaN?
- kccqzy 6y agohttps://github.com/rust-lang/rust/blob/2e0edc0f28c5647141bedba02e7a222d3a5dc9c3/library/core/src/ops/range.rs#L137 https://github.com/rust-lang/rust/blob/2e0edc0f28c5647141bed...
- hobofan 6y agoLet's try it! It's empty: https://play.rust-lang.org/?version=beta&mode=debug&edition=2018&gist=be2df2a59e0db7565c3e8b34e065947c https://play.rust-lang.org/?version=beta&mode=debug&edition=...
- dathinab 6y ago- Float ranges can't be used for indexing/slicing slices - Float ranges don't implement Iterator - Rust is clever enough to make is_empty checks NaN robust, so `is_empty` return true - contains checks if it's inside the stard bound and inside the end bounds, but whichever of the bounds is NaN is always not inside the bounds and as such the range won't contain anything (which matches with the behaviour of is_empty) So it works well. But float ranges are kinda useless for anything but the contains/is_empty methods.
- cryptonector 6y agoYes, but why does Rust's Range type not require this?
- the_hoser 6y agoI have a hard time taking Johnathan Blow seriously when it comes to PL design.
- eindiran 6y agoI don't know anything about Johnathan Blow, so could you explain why you have a hard time taking him seriously about PL design?
- the_hoser 6y agoBecause I can't download a compiler for his language and try out his ideas. I've designed fantasy programming languages, too, but you don't see me telling everyone on the internet that will listen to me about them, dragging other PL designers that have actually released working code through the mud as I do. It's a simple matter of put up or shut up.
- anoncareer0212 6y agoI neither know Jonathan Blow, nor understand why he must create a programming language to critique them - is he someone who works on programming languages as his day job, for some time, yet somehow has avoided enforcing his opinions on himself?
- the_hoser 6y agoHe's a video game designer.
- cyphar 6y agoHe's a video game designer who announced a new programming language called "Jai" in 2014, and no compiler has yet been released for it (but he discusses new language features on his Twitch stream)[1]. [1]: https://en.wikipedia.org/wiki/Jonathan_Blow https://en.wikipedia.org/wiki/Jonathan_Blow
- deleted 6y ago[deleted]
- estebank 6y agoFor literals, clippy will catch this by default: error: this range is empty so it will yield no values --> src/main.rs:2:9 | 2 | let x = 5..0; | ^^^^ | = note: `#[deny(clippy::reversed_empty_ranges)]` on by default = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges help: consider using the following if you are attempting to iterate over this range in reverse | 2 | let x = (0..5).rev(); | ^^^^^^^^^^^^
- pornel 6y agoMay be worth moving to rustc, because that's not just bad style, that's incorrect code.
- estebank 6y agoI don't disagree but it feels like the bulk of the likely problema would be when not dealing with literals.
- rtpg 6y agoI disagree, sometimes you end up building empty ranges in your code for valid reasons (as a result of math, for example). special casing a check on literals feels a bit like saying you can create an empty non-mutable vector.
- baby 6y agoI disagree, if this happens you want to document these edge-cases by failing early, this is very bad code if you don't and let the loop happen when most reviewers would have no idea if the result would be an unexpected behavior or not.
- estebank 6y agoIf I had a need for that I would write a surprised snake (0..0) instead.
- 6y ago
- dep_b 6y agoI love the design
- bluejekyll 6y agoI actually love this type. Specifically because you can build ranges over complex key types. For example, two keys in a BTreeMap can be used to define a range selector to collect items out of the map, it made me very happy to be able to do this: https://github.com/bluejekyll/trust-dns/blob/main/crates/server/src/store/in_memory/authority.rs#L206 https://github.com/bluejekyll/trust-dns/blob/main/crates/ser...
- ShroudedNight 6y agoThe example you provide appears to be using the same type for both beginning and ending values, and would also appear to explicitly provide an ordering (<=). Maybe I missed something, but that level of abstraction did not seem to be what was under indictment in the article, but rather scenarios where the types of first and last were disjoint, or the values were incomparable.
- tomjakubowski 6y agoRange<Idx> only has one type parameter. Thankfully, you really cannot have a range with an i32 on one end and a String on another. It's also not possible to call methods like contains() on Range<Idx> unless the type is (Partial?)Ord, because of the constraint on Idx in the impl which defines those methods.
- djur 6y agoSeems to me that you could still have this syntactic sugar if you use a separate type for iterable ranges and non-iterable bounds and then have them both implement RangeBounds.
- bluejekyll 6y agoI think you’d still want something where all the types Barrow to something consistent type. Otherwise, PartialEq/PartialOrd becomes complex to reason about. I haven’t tried this, but maybe the Idx of Range could be Borrow, much like the get requirements on HashMap.
- deleted 6y ago[deleted]
- cmyr 6y agoI broadly agree with this, and am most frequently confused by the fact that a `Range<T>` isn't `Copy` even if `T: Copy`. I feel this was a small design mistake (having `Range` itself be an interator instead of implementing the `IntoIterator` trait) but there is a proposal to fix this that looks like it wouldn't be breaking, and so should be possible: https://github.com/rust-lang/rfcs/issues/2848 https://github.com/rust-lang/rfcs/issues/2848
- saghm 6y agoYeah, this sounds like it could be fixed with a one-liner without any breaking changes: `impl<T> Copy for Range<T> where T: Copy {}`. That being said, I can understand why this might not necessarily be desirable for some people; Copy is generally reserved for types that are "small enough" that implicitly copying the bytes will be cheap enough to ignore. It might seem obvious that a struct containing two types that are Copy should also be cheap enough to be Copy, the line has to be drawn _somewhere_, and for any number of bytes that is chosen to be the threshold where the cost is too high, it's pretty easy to pick a size that's below that threshold but a struct containing two instances of that size is above the threshold. Because of the unclear boundaries for that sort of thing, I generally tend only to derive Copy on things in my code that thin wrappers around existing types that are Copy, e.g. `struct Foo(i32)`.
- cmyr 6y agoThe reason this can't be done quite as you describe is outlined in the linked issue: if something implements both `Iterator` (as Range does) as well as `Copy`, then you can end up in situations where you accidentally end up copying an iterator you think you're mutating. A good example is provided here: https://github.com/rust-lang/rust/issues/48649#issuecomment-478350940 https://github.com/rust-lang/rust/issues/48649#issuecomment-...
- saghm 6y agoHmm, interesting. I honestly don't think I've ever really used an iterator like that, but I can understand why that would be considered undesirable. It does seem like having Range implement IntoIterator rather than Iterator would probably have been best, but that's tough to fix now.
- josefx 6y ago> Range<Vec> requires the borrow, so the vastly more common Range<usize> etc. So I have no idea about rust, but in c++ a big feature of templated code is that you can make type appropriate specializations, is that not possible in Rust or did its standard library maintainers just sleep on the job?
- sushisource 6y ago"Sleep on the job" might be a bit unfair. Specialization doesn't exist but is in the works.
- akiselev 6y agoIt's been in the works for years and there's no end in sight. IIRC sound specialization might not be possible until some precursor work for GATs is complete. Last I checked (a month or two ago), the specialization feature warning even included a bit about how it can crash the compiler (the crashes were the reason I gave up on specialization two years ago) Edit: Turns out theres been some progress since I used it last! Feature `min_specialization` seems to not be a non-crashing subset of specialization [1] [1] https://github.com/rust-lang/rust/pull/68970 https://github.com/rust-lang/rust/pull/68970
- edflsafoiewq 6y agoRust has specialization but I don't think you can change the signature like that. In C++ of course it's the callee that decides how arguments are passed. But it would also be uncommon in C++ to specialize to pass by value. Generic code normally just passes T by reference, ex. max, push_back. Since this is transparent to the caller in C++ though, you don't have to write the &s.
- as_keyof_typeof 6y agoThe design of this website is fun. As someone in the Pacific islands, the background image looks like lauhala [1] to me. [1] https://en.wikipedia.org/wiki/Lauhala#Weaving https://en.wikipedia.org/wiki/Lauhala#Weaving
- b0rsuk 6y agoWhy not make the compiler issue deprecation warnings and provide a few complimentary, specialized range types? It sounds like it needs to be broken down.
- matklad 6y ago>This does mean writing for n in (1..10).iter(), but Rust already requires that for collections, so it’s more consistent. Even this needn't be the case. `Range` can implement `IntoIter` to plug into `for` loop syntax. Another related problem is that `SliceIndex` (https://doc.rust-lang.org/std/slice/trait.SliceIndex.html https://doc.rust-lang.org/std/slice/trait.SliceIndex.html) trait, which is used to implement indexing, is perma-unstable. So, even if you build your own better range, you can't make it play nicely with slices.
- dathinab 6y agoThe problem is that his/her proposal also says that start <= end needs to be enforced (or non of his "faster" things would work). Now consider the usability nightmare `(1..10).unwrap()` would be and `&slice[(1..10).unwrap()]` and `(1..10).and_then(|range| slice.get(range))` instead of `slice.get(1..10)`. That's the actual problem, not that `Range` implements iterator. Oh and most ranges are used ad-hoc (created and then directly consumed) so for many use cases going with Range + IntoIterator would increase the overhead. Besides that while SliceIndex is perma unstable, `Index` is not so if you control the container you can make it work alternatively you can always do `my_range.index(slice)`.
- ridiculous_fish 6y agoAuthor here. In my idea, x..y would be like x+y: not fallible, but may panic. There would be a separate fallible function for creating ranges, analogous to checked_add. (Of course unlike x+y, x..y would require checks in release builds too.)
- matklad 6y agoAgree with you that there won't be any problems with panicking in this setup. Although, it'll require some non-minor api adjustments: at the moment, Range's fields are public. To maintain the invariant, we would have to make them private and provide getters. Which we actually already do for RangeInclusive, because of that extra bool field. Which is an inconsistent mess :)
- 6y ago
- CodesInChaos 6y agoMaking `InclusiveRange` an iterator (instead of merely an IntoIterator) was definitely a design mistake and `Range` probably shouldn't be one either. Adding a `Copy` constraint seems a bit weird to me, since I expect those borrows (e.g. on `contains`) for consistency with collections. Deriving `Copy` (i.e. implementing it when the index type is `Copy`) should be enough. One small mistake in the article is that you need a `PartialOrd` constraint not a `PartialEq` constraint to ensure start <= end.
- ridiculous_fish 6y agoThanks, mistake has been fixed.
- saagarjha 6y agoImplementing PartialOrd for Vec is a fairly strange choice to be honest. I'm curious why lexicographic ordering was chosen for this.
- IshKebab 6y agoI think that's what you would expect. Why do you think it is strange?
- saagarjha 6y agoI don't know, I just find the concept of lexicographically ordering vectors something that isn't really useful. Can you think of examples where this might be something you would want?
- theon144 6y agoMaybe not specifically for vectors as their size is dynamic, but most certainly for tuples, ex. "sort by last name, first name, city..." (I can also think of situations where you'd pass around strings as vectors of characters, but I think that's a bit of a stretch.)
- ccccc0 6y agoIf you ever create a BTreeSet<String>, it will use String's PartialOrd, which is directly #[derive]d from Vec<u8>.
- saagarjha 6y agoI have nothing against String having PartialOrd. I don't see why this means Vec<u8> should be PartialOrd–especially because treating a String as a Vec<u8> sounds very wrong.
- pwdisswordfish4 6y agoWhy does it sound wrong? Since strings are UTF-8, this just boils down to lexicographical ordering of code points. Ironically though, that should be actually more objectionable than ordering arrays by lexicographical order of elements, because the underlying code point order is pretty much useless for any other purpose than having some arbitrary deterministic order. Meaningfully ordering strings is an inherently locale-dependent operation, and PartialOrd has no way to take this into account.
- leshow 6y agoI disagree, I think the fact Range is polymorphic is extremely useful. Perhaps if your only interaction with it is writing a range of usize's. The only real 'pain point' I've ever had to deal with is passing `&`, but I'd hardly call that a pain point.
- andy_ppp 6y agoI can’t even figure out how to iterate over a Range of Vectors? iter and collect both don’t exist... Google doesn’t seem helpful either. I don’t know any rust but it seems unforgiving at the first hurdle here :-)
- leshow 6y agoThere are plenty of hurdles in Rust, but I wouldn't call this one of them. What exactly do you expect an Iterator for a range of vectors to do?
- andy_ppp 6y agoTraverse the entries?
- dathinab 6y agoIt's not a range of an vector but a range which start/end are defined by vectors. The best thing I can come up with is that such a range defines a path from one tip of a (geomotric) vector to another vector and because vec in rust can have (theoretically) usize elements it's in a usize::MAX dimensional space ;=)
- leshow 6y agowe're talking about a range of vec![8,9]..vec![1,2] for example. What does 'traverse the entries' between those two values mean?
- javajosh 6y agoNot really a Rust programmer, but I would expect a Range of vectors to operate something like a vector of Ranges. So if you say [0,10]..[20, 30], the length would be twenty, and the elements would be [1,11], [2,12]... Presumably if there was asymmetry in the values like [1,10]..[20, 50] you'd end up with...something? Not really sure what it should be, there are lots of options!
- leshow 6y ago
- Ericson2314 6y ago> This is abusing the borrow checker as a bad linter. Range undermines the story of lifetimes and ownership, making the borrow checker feel arbitrary. This is the key part. You really shouldn't try to "censor" the math to "help" users. It just causes more pain. I'm recently been annoyed with the push back against "DynSized" again, which IMO is a symptom of the same thing. "DynSized" is the natural way to generalize Rust's notions of size/layout so we can implement custom DSTs, but people are scared of another ?Trait making Rust "more difficult" or "more complicated", so we're going to get some crapped up epicycles plan if we get anything at all. ------- Incidentally the Jonothan blow quote below is full 180 misapplied. The issue is people trying to dial back something, not push it to it's natural conclusion, because they don't like the natural conclusion. Too bad, don't do that.
- WhyNott 6y agoAre you sure about this? I've heard the quote applied specifically in the context of RAII, where he complained that there is no such thing as a generalized "resource", and that the same mechanism for handling memory access should not be used for file handles and texture maps. I don't have a link right now, but I'm pretty sure it was in his first "ideas for a programming language for games" video back in 2014. Seems to me like this range situation would be fairly analogous to that.
- cultureswitch 6y agoI remember his video on RAII. My opinion on his take is that it's pure tosh. Incidentally I also hate Golang... He's used to languages with thin abstractions like C where it's basically impossible to get anything done fast without dissociating allocation and creation (and in particular, allocation and mutation). Modern languages and Rust specifically address that problem by letting you write clean programs with the illusion of immutability but still basically mutating state all over the place in the actual executable. And that's if you care that much about speed. Even slow Rust is pretty fast, and the readability/consistency/maintainability benefit of RAII is immense compared to the tiny speed gains.
- dathinab 6y ago> A practical problem is writing correct bounds checks. For example, consider the get_unchecked function on slice - it says “an out-of-bounds index is undefined behavior” but never defines what out of bounds means. So how does one even call this function safely? Except that it's well defined: If you start or end after then end of the array/slice/vector. The same way it's done for any other array/slice/vector access. Could the documentation of this nightly experimental API be better, sure. Is it undefined or unusable? No it's as well defined as "an array index being out of bounds". Oh and it's only defined for Range<usize> before you wonder there is no confusion with any ranges of custom types, this a method of a trait implemented for only for Range<usize> which generalizes the indexing of slices and of which everything but the name is currently unstable/experimental.
- viraptor 6y agoSo my first thought was "hey, why doesn't it take AsRef instead?" Then I checked the rust playground and... AsRef is not available for simple numbers, which I find confusing - since you can always reference an int, why isn't it?
- coolreader18 6y agoI guess the same reason that `impl AsRef<T> for T` isn't implemented. (Not sure exactly what that is, but that would probably be a better question to look into, since you could argue that `AsRef<T>` should be manually implemented for any std type T with your argument)
- ComputerGuru 6y agoI don’t think they’re related. The blanket AsRef<T> impl doesn’t exist because that would prevent you for providing different behavior (or blocking this behavior) for any of your types (eg smart pointers/containers) because rust doesn’t yet have impl specialization or override.
- kobe_bryant 6y agooh wow I love your insane hand holding a scroll website
- saurik 6y agoGiven how general Range is, how does it even implement a len() which has the behavior in the article? (I tried looking into the documentation for this and came up short on Ranges even having len; I see it provides a TrustedLen to other things via a trait, but that seems unrelated, and it has some len related to an overload of one specific iterative type, but I feel like I must have misunderstood it.) Either it assumes subtraction, which should discover the negative length, or it would have to iterate the distance, which would take a ridiculous amount of work to get from 5 to 0. How is len() there at all, much less able to return 0 for 5...0? (FWIW, in my libraries like this, I have a different type for things that have a start/end for iteration from things which have a start/length for indexing, as they are often extremely different concepts and are used for different purposes, even if it feels like the former is a conceptual superset of the latter.)
- ratww 6y agoRange implements the ExactSizedIterator trait, and this is why you can call `(0..5).len()`. When you call len() the ExactSizeIterator just calls size_hint [1]. The size_hint is implemented here [2]. This is where the "start < end" check happens. [1] https://doc.rust-lang.org/src/core/iter/traits/exact_size.rs.html#71-134 https://doc.rust-lang.org/src/core/iter/traits/exact_size.rs... [2] https://doc.rust-lang.org/src/core/iter/range.rs.html#500-552 https://doc.rust-lang.org/src/core/iter/range.rs.html#500-55...
- MaulingMonkey 6y ago> Given how general Range is, how does it even implement a len() which has the behavior in the article? len() is hidden in ExactSizeIterator, which is implemented for a bunch of Range specializations: https://doc.rust-lang.org/std/ops/struct.Range.html#impl-ExactSizeIterator https://doc.rust-lang.org/std/ops/struct.Range.html#impl-Exa... Expanding the docs node and following the [src] link for len, we can see it's using ExactSizeIterator's default implementation, which invokes size_hint: https://doc.rust-lang.org/src/core/iter/traits/exact_size.rs.html#99-107 https://doc.rust-lang.org/src/core/iter/traits/exact_size.rs... ExactSizeIterator only requires Iterator: https://doc.rust-lang.org/src/core/iter/traits/exact_size.rs.html#71 https://doc.rust-lang.org/src/core/iter/traits/exact_size.rs... And sure enough, that's where size_hint is implemented in Range: https://doc.rust-lang.org/std/ops/struct.Range.html#impl-Iterator https://doc.rust-lang.org/std/ops/struct.Range.html#impl-Ite... -> https://doc.rust-lang.org/src/core/iter/range.rs.html#515-522 https://doc.rust-lang.org/src/core/iter/range.rs.html#515-52... Which in turn relies on steps_between, which is implemented via macro for various integer types: https://doc.rust-lang.org/src/core/iter/range.rs.html#240-247 https://doc.rust-lang.org/src/core/iter/range.rs.html#240-24... https://doc.rust-lang.org/src/core/iter/range.rs.html#360-372 https://doc.rust-lang.org/src/core/iter/range.rs.html#360-37... https://doc.rust-lang.org/src/core/iter/range.rs.html#388-404 https://doc.rust-lang.org/src/core/iter/range.rs.html#388-40...
- marshray 6y agoAlso, fix the problem that Range<u64> can't do .len(): fn main() { let r = 0u64 .. 1; let n = r.len(); | ^^^ method not found in std::ops::Range<u64> println!("Hello, world! {}", n); } https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=510eec267d1d72a2c87f1ad438fcb905 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- dathinab 6y agoInteresting: .len() is not the length of a slice induced by the range or the distance between start and end but instead it's the len method form `ExactSizedIterator`, which in turn is a "special case" of where `Iterator.size_hint()` is known to return a correct value. The thing is `Iterator.size_hint()` does return a size, which is usize. So `Range<u64>` can only implement `ExactSizedIterator` on 64-bit targets, which I guess is why someone decided that it's better to not implement it (at all) to not hinder portability of libraries as it would be quite easy to accidentally write a lib not working on 32 bit. Not sure if that is the right decision tbh.
- MaulingMonkey 6y agoIt is an interesting, awkward edge case. But the utility of a u64 length might be rarer than you think. `Range<usize>` will be more common for wrangling memory sizes, and will handle 64-bit sizes on 64-bit targets - so the utility of Range<u64> would mainly be for larger-than-address-space sizes, which probably means falliable I/O calls. I've been considering upstreaming a trait into the read_write_at crate to provide std::io::Result<u64> lengths for Mutex<impl std::io::Seek> / std::fs::File (on platforms where length is available without mutating seek position - such as on windows via file.metadata().map(|m| m.file_size()) per:) https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata https://doc.rust-lang.org/std/fs/struct.File.html#method.met... https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.file_size https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataEx... Context: multithreaded reads of zip archives & https://github.com/vi/read_write_at/issues/1 https://github.com/vi/read_write_at/issues/1 & https://github.com/MaulingMonkey/vfs-zip https://github.com/MaulingMonkey/vfs-zip There's a whole slew of u64 offsets and sizes... the occasional subtraction to calculate a maybe-larger-than-memory size doesn't seem like that big a deal. Occasionally there are methods implemented for it - typically named "file_size()" instead of "len()" though.
- crazypython 6y agoEnter Dlang, a static systems language with ranges. https://tour.dlang.org/tour/en/basics/ranges https://tour.dlang.org/tour/en/basics/ranges
- Rusky 6y agoThose are not even the same kind of "range"...
- ShroudedNight 6y agoRange's idiosyncrasies and the antipathy to improving its ergonomics manifest in the issues raised against the project have been a capstone mental block to my investing emotionally in Rust. I continuously really want to like the language. The premise is good, a lot of the ideas are really compelling, but when it comes down to aspects I disagree with, I get a strong sense of Rust demanding that users subjugate themselves to all the choices and opinions of the architects, and is (are?) hostile to the notion of a user expressing themselves through their tools, and not just their output. As a result, every time I interact with Rust my hackles end up getting raised and so far it would seem that a (currently absent) existential motivator would be required for me to get past it.
- Ygg2 6y agoSure. But to solve those idiosyncrasies, you need to abandon backwards compatibility or introduce conflicting syntax.
- thayne 6y agoNot necessarily. A new type could be introduced and the old one deprecated. Even without introducing a new type, it could be improved in a backwards compatible way. For example, derive the Copy trait, and add a contains method that takes an owned value instead of a reference (at least as long as the Idx type is Copy).
- Ygg2 6y agoPretty sure they discussed this change on Reddit (https://www.reddit.com/r/rust/comments/ix751t/my_least_favorite_rust_type/ https://www.reddit.com/r/rust/comments/ix751t/my_least_favor...): Here is the discussion > In 2021 edition the compiler team could change the meaning of (3..5) to resolve to a new type. Then if you wanted the old type you have to request it by a fully qualified name or something. And the responses was: > That would be pretty awful. I think it would be better to change IntoIterator and warn for differences in copy and intoiterator in both editions so you can write portable code.
- cultureswitch 6y agoAfter reading this I'm legitimately shocked at how horrid this is considering the cleanliness of the rest of the language.
- brundolf 6y ago> Is this backwards range valid? Its len() is 0, it contains() nothing, it yields nothing as an iterator. But if you try to use it to index into a slice, you get a panic! So it looks valid but is primed to explode! Rust has comparison traits... why aren't those involved here? It seems like it would be straightforward to ensure that any Range's start and end can only be Ord's, and that the first value must be < the second. > Range<Vec> requires the borrow, so the vastly more common Range<usize> etc. forces it as well. Not sure why this has to be the case. Why the implicit reference? Why not, when you want to use references in your range (a fairly exotic usecase), you have to do so explicitly? &vec1..&vec2
- eddyb 6y agoI kept grumbling about the `Copy` limitations on iterators for years, but at the end of the day, someone has to write the lint for "iterating on a copy of a variable" and I don't think that has happened yet. But unlike the other complaints this one is still fixable, with zero backwards incompatibilities. All we'd need is someone to implement that lint (in, say, clippy). What it'd need to do is look for `IntoIterator::into_iter(x)` calls expanded from `for` loops, where `x` is a variable of a `Copy` type. And maybe look for mentions of the same `x` after the loop. EDIT: left a comment on the GitHub thread, I might be misremembering https://github.com/rust-lang/rfcs/issues/2848#issuecomment-696502431 https://github.com/rust-lang/rfcs/issues/2848#issuecomment-6....
- axaJay 6y agoHonest question: Why is Rust preferred by trannies?
- arcticbull 6y agoI, uh, what?
- michaelmcmillan 6y ago{ Start, Stop } - { Begin, End } = ∅
- tomsmeding 6y ago> a Range with start 0 and length 0 may be out of bounds. Is this indeed so? Presumably on an empty slice, but then `get` would just return the empty slice again?
- bugmen0t 6y agoIf Range doesn't require PardialOrd, I'm not sure I know what Range is at all...
- fluffything 6y agoI don't even know what PartialOrd and PartialEq even are, mathematically speaking... The docs about these feel written by someone who knew that "some API like this" would be a good idea, but that somehow never managed to flesh out what these traits should semantically imply. Which is kind of dumb, given that there was _excellent_ prior art about this when Rust was created (Elements of Programming, From mathematics to generic programming, the C++ standard library and the dozen papers about operator<=>, ...). And that's one of the things I dislike more about rust. The way to overload operators, like +, or <, uses trait names, like Add or PartialOrd, which suggest that these operators have certain semantics (particularly when using Add in where clauses), but in practice they lack any semantic meaning and are just syntactic things. Which is why, e.g., the standard library implements "Add" for strings. That doesn't mean that it implements "Addition" for strings, but rather that it overloads the Plus operator. And in the String case it does so to implement "Concatenation". Which is IMO super dumb, because they could have just fixed this by naming the `Add` trait `Plus` instead, which is what languages that do the same thing, like C++, already do (`std::plus`, `operator+`, ....). You could argue that using `+` to implement concatenation is "bad", but it is way less worse than using "Addition" to implement concatenation, which is what Rust ends up requiring everybody to do because that's just how you overload the `+` operation.
- xondono 6y ago> I don't even know what PartialOrd and PartialEq even are, mathematically speaking... They are binary relations, just some that are more "niche" than the more common ones. PartialEq actually has a link to it's mathematical definition on the docs [0]. [0]:https://en.wikipedia.org/wiki/Partial_equivalence_relation https://en.wikipedia.org/wiki/Partial_equivalence_relation
- fluffything 6y ago
- sacado2 6y agoThe worse bug I fought in one of my rust programs was when using the range type. I needed to go from n to 1 included, so, from n included to 0 excluded. Of course `(n..0)` didn't work, so I tried `(0..n).rev()`, which didn't work either (first, you generate all numbers in [0;n-1], then you reverse the list), and I can't blame the language, it makes sense, but it's highly unintuitive (in python for instance, `range(n, 0, -1)` does what is expected). I'm not a seasoned rust programmer though, is there a better, more explicit way to do what I attempted there?
- yodelshady 6y agoI think (1..=n).rev() will do what you want? A .step_by() that accepts negative values a la python would be nice, but I can live without. I'd love a REPL for this sort of thing though - a website just isn't as good.
- sacado2 6y agoThank you! I didn't know about the `=n` syntax.
- deleted 6y ago[deleted]