#Things you'd love to see in Rust 2.0
1779 messages · Page 2 of 2 (latest)
just add dependent types to rust 
Duh doi
personally i prefer my code only takes one day to type check
sorry
without genuine inductive types (including strict positivity checking), i feel like any Rust system including type theoretic proofs won't actually be useful
recursive generic bounds that don't overflow
An std lib that contains more basic features so I don’t have to use crates for basic programs, like a random number generator
counter point, the rand crate has changed its api a lot over the past few years alone. If the std lib included random number generation we would likely be in a situation like python's std lib, "Oh yeah that works but it has been deprecated for years, you should use this instead"
yup, rand has changed quite a lot
Extended documentation for Rust's Rand lib
you can see in the update section
every major release makes things a bit different
plus with a dedicated crate you can jam in more stuff like new random algorithms
i don't think most random libraries in stds have the ability to specify the random generator and distribution for the output
C++'s <random>?
yes, but that's just one example
like JS's Math.random
is there anyway for you to configure what it does? no
Although C++ does have the extremely dumb restriction that you can't generate random bytes
Java's Random/SecureRandom.next*
the what. 
The result type generated by the generator. The effect is undefined if this is not one of
short,int,long,long long,unsigned short,unsigned int,unsigned long, orunsigned long long.
Notice the lack of char, signed char, or unsigned char
Apparently, the justification is that they're "character types" and not "integer types"
lmao
this is what happens when you conflate "character" and "integer" types
although C integer types are already a mess
We don't need Rust 2.0 to add things to the standard library. That can be done right now. However, as mentioned, there are good reasons to keep Rust's standard library fairly minimal. Especially in these early days of Rust.
That said, there is definitely an interest in the standard library having something like a getrandom function that's simply a primitive wrapper around the OS function for filling a byte array with randomness. So it could happen one day. See also: https://crates.io/crates/getrandom/0.2.7
Especially given that std already accidentally exposes a worse version of that through hash_map
Just design good APIs 
true
for Rust 2.0 I'd like the standard library to be objectively correct on every matter
Tbh, I think we could use adding rand and a few friends to the libstd docs, clearly noting them as "this is available by adding the rand crate".
The crates under rust-lang on github are basically libstd (except not forever stable), but I feel people don't quite realize that and think they're "just" third party stuff
well, rand isn’t actually under rust-lang 
Huh? I thought it was
I wouldn’t consider crates like rand to be under libstd, because they don’t follow the RFC/FCP/etc process
Nevermind that, points stands for other crates :D
I would love for more crates to follow that process though
"libstd++", then? "Official libstd extensions?"
and be under the jurisdiction of T-libs
No, I think I even agree thatrand is truly a third-party crate
They should introduce the RFC process to crates if they want an “std++”
Which I think would be a very good idea actually
Ok, fine, but a seal of "this is reliable" would be nice
yes, that too
But I think the seal couldn’t be justified with the current development model of those crates
How would a seal of approval work? Would it just be a passive "the libs team trusts these authors to maintain this crate"? Or would it be something the libs team is actively involved in overseeing?
I’d prefer the latter
I think it should really be an “extended std”
and so should copy everything except #![feature]s and being built-in
ehhh the rfc process is more needed for the stdlib than external crates
i'm not entirely convinced it's needed when things aren't perma stable
id be tempted to try doubling down on the small std and outright remove std
put it all in crates.io
only have core be lang-provided
and make it totally clear that external deps arent a bad thing
keep increasing the usability of dependencies
You'll need to solve the security problem tho
People already complain enough now about how the average Rust crate has way too many deps to be easily auditable
std uses unstable features, too
agreed :) I think crev hints at some genuinely good ideas and isnt able to carry them far enough because of its small userbase
so you'd need to make stable all of them
yep :) some would have to be moved around
and what would the MSRV of std be
2.0 lol
i hope to god it's not something low
like
std can currently just invent a feature and then use it
pretty much
We also have several std lang items that aren't in core (such as "what does panicking do")
i mean like isnt std using yeet_syntax atm
std is actively trying to reduce the number of unstable things it uses.
^
in general, im assuming that questions like yeet and panicking are solved. because that feels like the point of a 'what does ideal 2.0 look like' thread
(also there're a decent number of features which you could model differently to features)
Some are solvable with temporary rustc-wacky-feature-x crates which cargo will only be able to resolve on compatible versions
to me, these r the more interesting questions
if we're shifting to totally requiring deps for anything meaningful, how can we make that the best experience we can?
to me, an improved crates story would be huge for a rust 2.0 (though we can probably do a lot inside 1.0 too :P)
For security, I really like the idea of the web of trust. I think it formalizes the ideas behind why we have more trust for std today: there's an idea that the code in the rust repo is being looked at by a number of trusted maintainers from the libs team
we already do require deps for anything meaningful tbh
if that's a first class concept in cargo, then the default install of rust can come with default trust for some rust team members
and so the user experience doesn't change much, but now also helps with trust levels of external crates that are popular enough for libs teams members to be interesting in giving them feedback
from my pov, that alone would be a big big shift
kinda agree :D this is a big part of why I think actually improving the ecosystem is more important than trying to reduce the need for dependencies
the way FOSS development works today, we're not going to get away from these dep trees
and dep trees are good imo
the alternative is vendoring
and vendoring turns 1 bug into N bugs
to an extent, yes
and then there're the real problems around increased code complexity
when you look at dep trees of a lot of popular apps and consider whats actually needed, the depth of the tree is normall like double what it should be, and there're 3x the libraries, and if it were all bespoke there'd be 1/4 the code
sometimes those numbers are a lot worse, too
developing ways to systematically control that is a worthwhile goal
tho im not sure what the solution will be
How do you define "what it should be"?
Or in general, what sources are you taking those numbers from?
the numbers are coming from a few projects which ive needed to slim down, which have given me some gut impressions of how much got removed
doing an actual survey of public projects would be fantastic, but a lot of work :P so im basing it off my intuitions in the meantime
but it's definitely not rare to cargo tree some of the projects ive helped with and be able to hack off >100 dependencies while only adding like 100 lines of helper functions to the main project
Ok, that does sound plausible
Though that is a lot of deps
How many were there overall?
thats for all sorts of reasons - features are sometimes enabled when they dont need to be, duplicated dependencies add up fast, and once in a while you really can just rewrite the small bit of a library that you need
in the ballpark of 450
it was a nasty case, but not particularly rare for current Rust application projects
i think veloren is pushing that
whew, yeah thats impressive
thats probably across the whole project
between the client and server apps
That sounds likely
and yeah, duplicated dependencies are more common than they should be
we're having the compiler compile the same chunks of code up to three times
Yay for semver incompatibilities
definitely need a process for propagating updates out into the ecosystem more painlessly
& I really like burntsushi's thing of retargeting previous versions of his crates to the new version
was called something like the semver trick?
but its brilliant
Wait, isn't the point of semver that if this is possible it happens automatically, and you can state that a breaking chante happened?
The semver trick refers to publishing a breaking change to a Rust library without requiring a coordinated upgrade across its downstream dependency graph.
Isn't this just making a breaking change without saying so in semver with extra steps
nope :)
it makes a breaking change to some part of the library
without affecting others
like
normally, foo-lib0.1::SomeType and foo-lib0.2::SomeType are different types
even if SomeType didn't change at all
basically you want per-item versioning
So, it's still a breaking change, and you didn't say so. So I may be broken by your stuff
SomeType didn't break
so rs mod cratev1_12 { pub fn foo() { /* complex logic */ } } // I want to publish a different api mod cratev2_0 { pub fn foo(v: u8) { /* complex logic */ } } // but the two implementations are *fairly* similar, and now consumers are having to compile both crates! // lets make one final update to v1 mod cratev1_13 { pub fn foo() { super::cratev2_0::foo(3) } }
and you're still marking breaking changes as breaking changes
Are you now? Wasn't the idea that you can use the changed item as its old version?
(this is also relevant to types like 522 said, but im focusing on the dependency graph perks atm)
SomeType didn't change at all though
pretend SomeType is some very commonly used item that didn't change
that's not why you made the breaking change
So I can update from 0.2.0 to 0.2.1, and this breaks me, because I'm now secretly pulling in a type from 0.3, which doesn't match the properties I need
(nope)
you made the breaking change to remove some deprecated functions or whatever
What is SomeType, 522?
Where are you taking that from
It's not in the link
did you read the example in the readme
i made up SomeType
I did
What I see is that a int32_t has become a uint32_t, and it has done so in the old versions too
c_void on 0.2 and 0.3 is identical
and i should be able to use a 0.3 c_void passing to a 0.2 crate
Ok, that is fair.
that's the trick
making c_void the same type
the thing that actually changed is a breaking change, yes
Thanks. That does make sense
old crates get patch updates that make them depend on newer versions
cool beans, youve got it :)
& the same trick is also great for dependency tree cohesion
every past version with a published "backport"
more formalised
has only one dependency
As opposed to the link, with, I'm sure burntsushi meant well, but this information, reading it again, is scattered across several paragraphs of "this would be a painful breaking change"
I wonder if we can automatically detect that two types are identical across crate versions
if we could standardise doing this for all version upgrades, and ensure that its done correctly with semver checks
thatd b v nice
we can, to a degree
its easy at the type level
but sometimes crates have untyped semantics which changing would be considered a breaking change
though that seems easy to break type invariants by mixing functions from different crates
which is why rust has avoided this feature so far
Uh, what are you referring to?
soo
I can't think of an example
// my crate 0.1
struct Foo(u32); // SAFETY: is always even
// my crate 0.2
struct Foo(u32); // SAFETY: is always odd
how do you not merge these?
/// you will always get double a
fn foo(a: u8) -> u8 { a * 2 }
/// new version of crate:
fn foo(a: u8) -> u8 { a }
yeah same idea
Oh, that
The function one might be not a problem, tho?
The type one is definitely an issue
the function one is definitely also a problem
any documented behaviour of a function needs to stay
without a major version change
if i have a log("foo") function which tells me that it will write foo to stdout, id definitely be upset if it started sending it to a text-to-speech engine in a new minor version
True. My point is just that if this was a method, you could still consider Self compatible with its old self, (tho you may want to say that behaviour depends on the caller's version)
well u cant really. at least not with current rust
and it would lead to weird questions around soundness if you tried to add it
I'm extrapolating from my idea to allow some libstd change on editions, such as changing Vec::push's return type.
That should be trivial
to return a &mut T?
Yup.
hmm sure
there's state invariants inside the vector that need to be maintained, but you can change each function
bc they're basically being "namespaced" by edition at that point
Yup.
youd have a v2021::Vec::push and v2024::Vec::push
With the two Vecs being the same type.
yup
but i do come to the conclusion that we cant make the backporting thing automatic
because APIs can change, the backporting just works because you can normally define the old API in terms of the new one
as long as you're generally adding features over time
I feel this could be solved with attributes. Maybe you could cfg on your crate version?
But of course, that would be ugly to read
sorry, can u explain what ur solving?
I'm formalizing the semver trick a bit more
#[cfg(crate_version = ..)]
pub enum c_void = ...
#[cfg(crate_version = ..=2.0)]
pub const EVILFT_AIO: u32 = 2;
#[cfg(crate_version = 3.0..)]
pub const EVILFT_AIO: i32 = 2;
This may want special compiler/rustdoc handling, so perhaps it could be a proper attribute rather than fall under cfg's "umbrella"
ehh that feels weird
since like
do you add these attributes to every item
or only when you need to do the trick
that seems verbose af
i think a #[same_as(crate::c_void, 0.2)] or something would be better
as an attribute on the 0.3 c_void
hmmmm
That is probably a better idea, yes
The "attributes on all the things" idea was inspired by std, but it is ugly
Maybe this?
This would allow a set of "blessed" extras which come with the toolchain but can still version independently of stdlib
come with the toolchain
now you have more stuff that needs to be updated quickly if there's a security patch needed
serde_json DoS (not unreasonable)?
update your compiler
std doesn't really deal with untrusted input that much
...as opposed to the rust 1 status quo where you have to run cargo update to get updates for crates?
The crates would still be available to update from crates.io like usual
sure but where did you get your compiler
package manager or rustup?
also
security updates for std are pretty rare
i'm not sure how big this library is proposed to be but
Presumably they'd be more common for these contrib crates
Not necessarily
Having them along the standard rust install does not preclude having them on crates.io
so
would we ship security updates to them?
or would it just be "eh they get updated when they get updated, every 6 weeks"?
if we do: that's a lot of noise and repeated updates
if we don't: why are we even shipping them
if they're unsafe to use (sometimes)
They'd normally be shipped on the 6-week release cycle, but security updates can occur in the middle of a release cycle
okay so
like
what's the benefit
It's supposed to provide some of the benefits of a large stdlib
While not having its drawbacks
It'd also be a start to get people to not treat ecosystemic dependencies as universally toxic, by providing a sampler of crates that are outside std but well trusted and already on your system to use
you're already inheriting one drawback of a large stdlib, updating it is tied to the compiler version
making using an old compiler (see: debian) more dangerous
using debian stable is already dangerous enough 
I think we can agree to disagree based on the tradeoffs involved 🤷
okay like
does it have to literally be shipped
or can it be a version number
and then it's downloaded from crates.io as needed
The world if debian didnt exist
There exist other distributions that maintain and provide truly archaic versions of things
Such as red hat enterprise linux
So all that would be different is that there would be a debian-shaped hole in distro history, with other distros taking up its space instead
to_lowercase/uppercase should return a Cow<str> rather than always allocating a new String
a relatively minor change, but breaking nonetheless
at least no one is compiling rust on there
right? right?
Strings should have .lowercase() returning a lending iterator over (&'source str, &'this str) and we should have a .collect_str() function 
an iterator over Either<&'source str, char> would work
yeah, but that’s more dynamically typed
actually, I suppose it’d be mostly the same
returns the longest unchanged sequence possible along with the next lowercased character
this is Rust 2.0 we’re talking about :P
LendingIterators would be stable
actually it might not be a good idea
doesn't mean it's a good idea to have lending iterators t b h
I’d probably be able to think up a better API if I studied the Unicode sources

lending iterators are great
"sorry fam you can't move this iterator"
you generally don’t move iterators a lot
Not while an item exists anyway
this API would be pretty annoying to use though
How so?
Well, if the old API was still provided it would be fine
But it's definitely more annoying to use than the old api
Having to manually work with the iterator
Most of the existing Iterator API would just work with lending iterators
Though implementing the methods would take more effort.
You lose collect, and even that one can come back with some bound for "the associated type doesn't actually borrow self"
You'd technically also lose chunks, which doesn't exist anyway on arbitrary iterators
In general, all methods that pull at most one element at a time, which is basically all of them, would work fine
itertools has it, would that break?
We're assuming Rust 2.0, aren't we?
In Rust 1.x, they could solve it using whatever mechanism we add to solve the collect problem, if any
It just needs a bound on "the associated type doesn't use its generic lifetime", which I believe is possible but don't remember the syntax for
If we made this bound implicit if you don't even name the GAT lifetime on the associated type (so, like all existing Iterator impls already do), that'd probably work out.
(not saying it's necessarily a good idea to make this implicit, but we could)
I love how this thread is all the regulars constantly forgetting that it’s supposed to be rust 2.0
evidently we have developed an instinct to always consider backward compatibility first thing
wouldn't rust 2.0 break things

it doesn't have to be changes that can only be made in Rust 2.0
the prompt just says things you'd like to see in Rust 2.0
which would obviously include things that could be included in the indefinite future of Rust 1.x, but aren't
Here is an example of running a program inside another program. normally a programming toolchain only lets a gigantic IDE debugger tool do this, but this is a built in feature of the Beads language, that one program can run another. You can freeze the execution of a program, and then rewind its state to some previous point.
While frozen, you ...
this ^
because its cool
have no idea how it would properly fit into rust lol
Time-travelling debuggers?
There's one that supports anything LLVM, but only on linux.
There's even a riir of that tool, though I don't know how good it is
I'm assuming you mean rr and rd (the rust rewrite), and they are quite neat
more the high level of reflection into the gui :D it'd be hard to manipulate debugger objects from Rust's runtime code
maybe more intuitive syntax for closure variable derefs..?
|&x| x why does this deref x lol
i talked about it in #990379665959043194 a bit
that's actually what prompted me to come here!
Really, it's just how pattern syntax works; &x takes a reference and then copies x by value
LOL
this is very good i like easy helper functions
So how exactly is this superior to Clone::clone?
Just the clarity?
seeing a clone in code automatically triggers a red flag in my mind unless its preceded by Arc:: or Rc::. I think that copying looking more explicit in code than just another clone is a good thing.
same principle as why iterators have cloned() and copied()
support for different casing without using the case decorator
?
I'm reading this as a complaint about needing to write #[allow(non_snake_case)] to get rustc to not complain about that.
What else would it do?
It's a pattern, patterns follow the rule of doing the opposite of the same syntax in an expression
If we removed some magical syntax, we could have let ref_a = Ref(a); and |Ref(x)| x
(I don't recommend doing this. Just pointing out it makes the consistency clearer)
#![allow(nonstandard_style)] will allow any crime you like.
forbid unsafe is good because it is a hard rule against writing it
if a project has forbid(unsafe_code), you can be reasonably sure they don't have unsafe
without needing to grep for it
also, some things that are unsafe don't have unsafe in the name
?play
#![forbid(unsafe_code)]
#![allow(dead_code)]
#[link_section = ".example_section"] fn uwu() {} //~ ERROR: declaration of a function with `link_section`
#[link_section = ".example_section"] static UWU: u32 = 5; //~ ERROR: declaration of a static with `link_section`
fn main() {}
error: declaration of a function with `link_section`
--> src/main.rs:4:1
|
4 | #[link_section = ".example_section"] fn uwu() {} //~ ERROR: declaration of a function with `link_section`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> src/main.rs:1:11
|
1 | #![forbid(unsafe_code)]
| ^^^^^^^^^^^
= note: the program's behavior with overridden link sections on items is unpredictable and Rust cannot provide guarantees when you manually override them
error: declaration of a static with `link_section`
--> src/main.rs:5:1
|
5 | #[link_section = ".example_section"] static UWU: u32 = 5; //~ ERROR: declaration of a static with `link_section`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the program's behavior with overridden link sections on items is unpredictable and Rust cannot provide guarantees when you manually override them
That's mostly attributes? Because there's no #[unsafe(...)]
yeah, pretty much
the workaround is to basically make them trigger an unsafe_code lint
so
if you forbid unsafe code, at least you won't be able to use them

or a flag/macro that fails to compile the code if it has an unsafe block
?eval mode=release
struct Footgun;
impl Drop for Footgun {
fn drop(&mut self) {
unsafe{ std::hint::unreachable_unchecked() }
}
}
let footgun = Footgun;
timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11: 9 Illegal instruction timeout --signal=KILL ${timeout} "$@"
forbid(unsafe)
oh nvm
did rustc emit an interrupt 💀
asm!("int 0h10") 
yeah, that exists thankfully
.
Why does this need to be in a 2.0? This could be added to any version of rust.
it doesn't
it would be nice to have some API changes to the formatting system too, though
Like, I often wish that doing {val:x} would still work if val didn't implement LowerHex—it'd just recursively apply to any of val's fields
Basically passing a configuration struct to Display instead of manually implementing 4 different traits
i think you're talking about dtolnay. i don't believe i've ever employed the semver trick. 🙂
- removal of
as
- Why?
- Wouldn't that be a breaking change?
I'd love to see variadic generic parameters if not variadic parameters in general.
- Wouldn't that be a breaking change?
Yes, this thread is for people to voice (potentially) breaking changes they'd like to see.
👆
@agile zealot see ^ and following discussion
oh wait wrong ping, whoops
@forest plaza
"^ and following"?
And yes, I realized this can include breaking changes.
Hmmm...
What if as did From/Into conversions as a replacement for its current behavior?
i am not convinced we need it as a keyword at all.
we can just call .into() when needed
it's extra syntax sugar with minimal added value
what do you propose for <X as Trait>::associated_function then
that stays
i'm only against as for value conversions
ah, so just removing as conversions
yea
I agree with that tbh
Also a way to convert integers to repr-enums. a Derive impl or some other macro would suffice really; it just sucks that this is thing that requires a dependency or macro_rules for
<X : Trait>::associated_function().
There.
: is used for bounds and type ascription
Now no as.
Now it can be used here for coercion.
:flextape:
this doesn't solve the problem i'm having. It's not that i have a keyword per se.
It's just its usage as a very funky .into() call
as i just said above, i don't think we need special syntax sugar for .into()
Right.
But now we could fully cobble the word away.
Just like we do in real languages.
Plus, it's a character shorter now.
¯_(ツ)_/¯
I mean, disregarding what you said, how is it as a standalone suggestion?
Disregarding what i said, i am firmly in a "whatever" on it.
I don't see the value added, but i also don't see any particular problems with it.
But i would guess people who are better versed in type theory might have slight issues with it, since the semantics of that <X : Type>::function() cast is slightly different from where X: Type bound
What cast? The as in <Foo as Bar>::baz is arbitrary syntax, it could in theory be any syntax (preferably unambiguous).
The key word I'd use to describe <Foo as Bar>::baz is disambiguation,
it disambiguates what trait and type combination you're getting that associated item from.
Yeah, that's not a cast
I just want something nicer for unsizing and float<->int casts tbh
sure we can call that "disambiguation", but that's still different from the where Foo: Bar or let x: Foo which is a "restriction"
Yes, they're different things
Eh, what is a disambiguation if not a restriction of the searched namespace
and a pile of secrets
That's a human
Wrong, we're piles of meat and ever-oxidizing iron-filled heart-juice.
AND a pile of secrets.
oop, my mistake! youre right
basic inheritance because it's actually useful sometimes
ehhh
when?
like im not saying it's not
but is it worth the complexity
and can it be gotten by different means
🤮
range types that are copy
Yeah I think it's unfortunate that we can't have a type be both Copy and Iterator without it being a footgun, but I'd much prefer ranges be Copy and IntoIterator than Iterator and Clone.
Though, hmmmm, that gets me thinking, we could just have range Iterators as a thin (non-copy) wrapper on ranges, something like:
// Not an Iterator, but is IntoIterator
#[derive(Copy)]
pub struct Range<Idx> {...}
// Plus all the other basic range types
// Not Copy but is an Iterator
// Maybe with some bound to limit it to RangeBounds types?
pub struct RangeIter<R>(pub R);
(I'm sure this has been discussed to no end elsewhere but if it has, I haven't followed the discussion)
- decl macros 2.0 would be awesome in general
- proc macros without creating a whole separate crate would be nice, too
please be more constructive in your criticism, this just comes across as judgmental and doesn't really contribute to the discussion
How about "inheritence lite"? (Struct embedding.)
wdym?
actually an easier way to just forward a lot of methods would be better probably
because that's kind of just budget inheritance
im not familiar with that term
That was a reference to this comment, if you were wondering, though:
#990793480999686154 message
I was basically joking in short.
oh ok
Easier method forwarding would be great, yeah
@last ocean @vale fractal
What is method forwarding?
(Also, I may kinda sound like an asshole, but yeah, I know, the double ping wasn't necessary, I just kinda felt like it in this case.)
I'm guessing it's something around the lines of taking some methods that a member-value has and allowing it to be called from the containing object?
idc about ping its fine
yeah
I mean, that just sounds like inheritance without getting the data-portion.
Idkay but that doesn't really sound like a great deal.
It sounds a lot better when you account for how common newtypes are
"newtypes"?
-- Even if it's "new types" I still don't really understand what you mean.
Also, it's not really inheritance: you can't automatically use the forwarder where the "inner type" is expected
"newtypes" is actually the proper term: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
Oftentimes used to get around the orphan rule
struct UserName(String);
that's a newtype
Which would be much nicer if newtypes didn't lock you out of all contained methods
I thought that was a tuple struct.
Or is a "newtype" a specific kind of tuple struct with only one member?
you can write it as a normal struct as well
a newtype is just a type that wraps another type and gives it some new meaning, but is often fairly transparent
What's the orphan rule?
"newtype" is the common term for this kind of struct. It's usually done as a tue struct with only one field
Makes sense. A lot like Go's type aliasing that makes a new type instead of sharing a type.
The point is that it's basically the same as the underlying type, but not quite. Sometimes it's to avoid mixing them up, so you do want to not forward methods. Sometimes it's for trait reasons, in which case forwarding would be neat.
Wish is something I wish Rust had.
That'd be cool.
"no implementing foreign traits for foreign types" and all similar limitations.
You can make a newtype around the foreign one, and now it's a foreign trait for a local type. Legal.
As far as I can tell, that's equivalent to automatically forwarding all methods in a rust-style newtype.
Honestly I don't care exactly how it happens. I just want types that have the same API but are different for the purposes of trait impls.
Why is this called the "orphan" rule?
That word shows no link to the situation.
Because it's meant to prevent "orphan instances", which is a term we copied wholesale from Haskell.
What if we got Go's type aliasing that makes a new type, and you just add impls to that?
.... And... what's an orphan instance..?
The other term is "coherence", because it's meant to ensure trait impls stay "coherent", aka you can't write ones that are illegal if more crates get added or if a parent crate makes a non-breaking change
A trait impl of a foreign trait for a foreign type, translating from Haskell-ese
You might notice this definition is cyclic. I don't know why Haskell named them that, unfortunately.
That's what I was referring to. It's equivalent to method forwarding.
For the reasons stated in that message, I support this.
I don't know why Haskell named them that, unfortunately.
It is unfortunate because I'm sure proper naming would probably boost my understanding here.
if the name comes from haskell, it's guaranteed to be weird and not good
It probably originates from type theory jargon, so I doubt it.
Call it "coherence" in Rust, it makes more sense
Oh boy.
We're trying to prevent incoherent (read: incompatible) impls from ever arising
Cyclic? Foreign trait and foreign type aren't defined in terms of orphan impls.
without the rules, i could implement Clone for regex::Regex and regex also implements Clone for regex::Regex already and now the compiler would be very confused what it should do if i ask it to clone a regex::Regex
And I mean, this would make struct embedding a much nicer task.
We can have forwarded types for some things, then put those in our compositions.
No, as in, I said
- "foreign for foreign" violates the orphan rule
- the orphan rule prevents orphan instances
- orphan instances are "foreign for foreign"
Coherence is analogous to solving the diamond inheritance problem but for impls.
That was the cyclicness I was referring to.
Which could alternatively have been solved by giving impls names, visibilities, and making you import/specify them.
Not judging whether it should have, but it's a possibility
So the cycle isn't in the definition but the motivation.
Yup.
The definition is perfectly reasonable
I just realized I was making a cyclic explanation, and phrased my acknowledgment of that incorrectly
1 - only write pointers
let mut arr:[u8; 5];
for i in arr.len() {
arr[i] = 6; //&write pointer
}
2 - generics being type const
struct example<T:type, E:usize> {
content: [T; E],
} //no const keywords in generics
3 - const args being generics
fn printnum <N:u8> {
println!("{}", N);
}
The second one is basically what C++ does, but I think type by default is nice because it's the most commonly used kind of generic
Also write only references (that must be written to) would be useful in some cases
Also called &out sometimes
ye, you can use them in undefined values
what
When used as a function parameter, it would be like an output parameter
no, generics should be like const fields
no i think i dont understand what you say
You might be interested in how the Zig programming language does generics
how
you pass types as compile time function parameters
I'm not very familiar with it, you have to look it up
that is better, i think, it does the lang easiest to understand
- it does the compiler easiest to understand
it makes a lot of tradeoffs for that simplicity
parametric polymorphism is fantastic and i wouldnt want to give it up in rust
a module like Python’s functools would be cool if it doesn’t exist yet
This kind of thing also seems similar to what I've heard about Dependent Types
It... Really doesn't. It trades one terrifying ball of complexity (type and trait resolution) for another terrifying ball of complexity (comptime evaluation)
Comptime evaluation in Zig is all the worst part of Rust's trait solving, type checking, macro expansion, and const evaluation, all at the same time, in a fixpoint loop
Don't get me wrong, it's a really cool feature. However, it's by no means a simple one.
Also, you give up on some extremely nice properties. Rust has a rule that if a generic function compiles, then it compiles for all possible type parameteres, no need to check it again for all of them (which wastes times and risks producing errors that you didn't notice, only your dependencies did, so now you have to breaking change to fix it)
I had a brief look at functools, and I think much of it already exists in rust? A lot of it I'm unsure about though.
- cache seems useful, though I'd expect a rust equivalent might have to be a bit more explicit and might lose some simplicity as a result.
- Partial is currying, I agree that's useful, though there's probably a crate for it and other function combinators.
- Cmp_to_key seems like it might be useful, but
if it does what I think it does, then it's probably trivially implementable with a lambda or a newtype that implements cmpI'll admit I'm not sure I understand it - Total_ordering looks like it does something that rust gives you for free (i.e. implement one comparison function and get the operators for free)
- Iterators all provide reduce already
- Singledispatch looks like it's just doing generics, which rust does by default
- I have no idea what updatewrapper is doing
yeah some is covered by rust already as you said. I probably should have been a bit more specific on what I would think would be good to add to rust from functools, instead of the whole module in general
post-monomorphization errors are possible in Rust (but they break this assumption, so they're usually pretty discouraged)
They're not exactly type errors, tho.
They're usually "this thing is too big for the hardware what the heck are you doing" or "Why do you have a type nested more than 256 times"
Well, you can effectively specialize on const generics by using associated consts
fn assert_non_zero<const N: usize>() {
struct NonZero<const N: usize>;
impl<const N: usize> NonZero<N> {
const ASSERT: () = assert!(N != 0, "N must not be 0");
}
let _ = NonZero::<N>::ASSERT;
}
Luckily, I don't think this is possible for regular type parameters without specialization
I haven't read this thread yet, but I'd love to see dependent types, obviously, since I'm the dependent types shill :P
Basically fuse an ITP and Rust together.
The ease of use of rust + the way higher expressivity and ability to formally verify code where necessary.
yeah im pretty positive dependent typing is going to be the norm
idk how long itll take
theres a lot of ergonomics to figure out
Kinda wish sort_unstable was 2.0 sort and that sort was 2.0 sort_stable
today, I naturally went to sort's docs and it basically says "you probably want to use sort_unstable" so I feel like that should be the one with the shorter name :V

does that emoji actually mean "but..." like its name implies, or are you just acknowledging
I have had the same thought before
But I think people had good arguments for making stable the default
But I can't remember exactly
Stable sort is the strictly more useful one, technically
It's a case of "if you only look at the docs for the minimum possible amount of time, or don't look at all, then you'll land on a sort that is correct for what you're doing"
C's rand is optimal for "Just quickly give me a number nondeterministically. Kind of. Maybe." but not for actual randomness
Math.random()
Btw, did you know that you should not use Math.random in JS as a crypto rng fallback?
Use it as your primary crypto rng instead to get more predictable results
is unstable sort dangerous somehow?
idk how it can cause unexpected behavior to move around equal structs
Choosing a sensible default choice for rand is hard, because there is no one real good solution for all cases
well, sometimes you need a stable sort
in which case an unstable sort would be wrong
And sometimes you need a stable sort but aren't aware that you need a stable sort
so basically they opted for the safer default
a stable sort is always correct where an unstable one is
the reverse is not true
but the docs on both should try and teach you the difference
I mean, they do, don't they?
for example, stable sorting ints (or most primitives) is useless
and clippy will tell you that
(to use unstable sort)
Fun fact: there will never be rust 2.0
And there will never be a web 3.0
But that's not the point of this thread
Oh no I derailed the thread
web ∞ plz
That gets me thinking, why not a fast::rand and a crypto::rand (placeholder names)
More specifically, what if we didn't have a single default for random number generation, given that there are at least two reasonable criteria for what a "good default" would do (high speed or high security).
High security is the only good default
I wish we had F: Fn(...) -> impl Trait bounds, that desugared into F: Fn<(...)>, <F as Fn<(...)>>::Output: Trait
tbf that might not need 2.0
It would make async closures infinity times easier though
F: for<'a> Fn(&'a [u8]) -> impl 'a + Future<Output = ()>
i wish you could dors fn foo(x: impl ToString) { ... } and you could do foo::<...>
or rather, i'd love to not have to explicitly name type parameters if i didnt need to
impl Debug for Wrapper<impl Debug> {
...
}
``` for example
Well, you can write equivalent bounds with the trait alias pattern and associated_type_bounds unstable feature:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=708d986e12d767748e234b875f249020
#![feature(associated_type_bounds)]
use core::future::Future;
trait Fn1<T>: Fn(T) -> Self::Ret {
type Ret;
}
impl<T, P0, R> Fn1<P0> for T
where
T: Fn(P0) -> R
{
type Ret = R;
}
fn foo<F>(_f: F)
where
F: for<'a> Fn1<&'a [u8], Ret: 'a + Future<Output = ()>>
{}
too bad that associated type bounds don't have a direct translation to the closure bound syntax
does that actually work? You will probably still get "not general enough" error
I think the problem with this is deeper than missing syntax
Well yes, but that's due to limited support for higher ranked lifetimes, not lack of support for impl Trait in return type position, both are orthogonal.
it's also be cool to have ```rs
struct MyPtr<T>(*const T);
impl Copy for MyPtr<> {}
impl Clone for MyPtr<> {
fn clone(&self) -> Self { *self }
}
Unconditionally Copy types can implement Clone with
fn clone(&self) -> Self { *self }
oh dur right
lmao i even have that in my code ijust forgot
impl Copy for MyPtr<_> {} I think this is too much syntax sugar
how?
you get '_ why not just _
also, related: why does impl Trait in parameters for a function preclude explicit generics when calling it
Having '_ for lifetimes and _ for types/consts is nice for visual disambiguation.
i cant tell if youre agreeing with me or not
why not just
_
sounded like you wanted_to work for all generic arguments
oh no
oh i see how you could interpret it like that whoops
i think '_ should stay as is, but i think it'd be nice to allow for _ as a placeholder for generircs you dont care for
in a similar vein
struct Wrap<T>(T);
impl Debug for Wrap<impl Debug> {
...
}
impl Wrap<Option<_>> {
fn is_some(&self) -> bool {
self.0.is_some()
}
}
``` etc
agreed, i've done this accidentally.. 3 times now i think
and am always disappointed when i re-learn it's invalid
that'd be my big thing
oh, and having piece-wise borrows would be cool
it'd have to be done in a way that flowed well; im not sure the semantics or if it'd even be possible, but it'd sure as heck be cool to be able to borrow different parts of a struct at the same time
This feels like you've completely dismissed my point, without addressing it at all.
Care to address why there's no room for two defaults, aptly named so that it's clear which one is fast and insecure, and which is slow and secure? Like, I accept that I may be wrong but you've basically just said, "no." without adding anything of value
Because the default (cryptographically secure) RNG is fast enough for most use cases/nearly all, and if you need faster, you can switch it after noticing in benchmarks that it's too slow for you
it's not slow by any means, to be honest
Thank you
like on my system it's even faster than some of the non-cryptographically secure RNGs :P
well, chacha8 is faster than pcg32
barely
they're both just about 2500 MB/s
Assuming you mean via separate method calls; you can do that via a single method call, but methods having an explicit "the return value partially borrows only field x " would be nice if we can get it cleanly.
The usual syntax strawman is fn foo(&{bar, baz}self) -> Qux
My interpretation thereof is that bar and baz are public aliases that can correspond to zero or more fields, such that a field is in at most one alias
how would provenance work there
is a &{bar}self allowed to be used to access fields that aren't part of self?
Sounds Like A Fun Problem
Hmmmm, yeah I was only thinking of the returns...
You may have to specify the subset that are used, and the subset that are tied to the return?
Like, terrible suggestion but:
fn foo(&{bar, baz}self) -> Qux<'bar>
If foo is allowed to access bar and baz, but the return only continues to borrow bar
That's essentially the same problem that we have today,
where &mut self methods that return shared references exclusively borrow Self with that shared ereference.
?eval
struct Foo(u32);
impl Foo {
fn bar(&mut self) -> &u32 {
&self.0
}
fn baz(&self) -> &u32 {
&self.0
}
}
fn main(){
let mut foo = Foo(10);
let bar = foo.bar();
let baz = foo.baz();
println!("{bar} {baz}");
}
error[E0502]: cannot borrow `foo` as immutable because it is also borrowed as mutable
--> src/main.rs:16:15
|
15 | let bar = foo.bar();
| --------- mutable borrow occurs here
16 | let baz = foo.baz();
| ^^^^^^^^^ immutable borrow occurs here
17 | println!("{bar} {baz}");
| --- mutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
Is it?
I was intending it to mean, it only accessed the named fields, so you could have a third unmentioned field already borrowed and still call foo
And after calling it, while the return is borrowed, you could call something that borrows baz
i forget if it's a soundness issue to make it a shared borrow, but it might be
Yeah, and having a way to reduce the scope(breadth?) of a borrow in the returned reference is also required in my example.
My expectation is that it'd still keep borrowing all the fields in the self argument otherwise.
I'm not sure I understand, possibly a poor example. Your example borrows .0 twice, so it would be illegal regardless
Oh wait
Nope, I gotcha now
I misunderstood your use of "exclusively"
how error handling should work:
fn index(arr:&[u8], x:u8) -> usize {
for i in 0..arr.len() {
if arr[i] == x {return i}
}
throw "no index found";
}
fn main() {
let arr = [1, 2, 3, 4, 5];
let result = index#(&arr, 6);
// #: Result<index::Err, usize>
// !!: panic
// %: unreachable panic
// nothing: defined by cargo.toml (panic default) (can't be #)
match result {
Err(r) => {println!("ups, code error {}", r);},
//printed: "no index found"
Ok(r) => {println!("{}", r);}
}
}
change my mind this is better, no _unchecked functions
you misspelled yeet
Yeah, this is terrible
I don't really understand
this is promoting unwrap() and unwrap_unchecked() to builtin syntax?
I mean, I guess that's fine
though the implicit result type is a nono
- absolutely nothing in
index's signature says it's fallible - strings are not good errors, be it in terms of performance or usability. Use a matchable error, at least.
- Having magical syntax for results is unnecessary
unwrapis already overused enough without abbreviating it to!!.- what even is an "unreachable panic"?
- it absolutely should not be the case that not doing any error handling does an implicit
unwrap.unwrapis already overused enough,!!would be worse,would be worser still.
All of these things are bad, as far as I'm concerned. Some are worse, but none of them is better than the status quo
Also, how does this have any effect on the existence of *_unchecked functions?
i guess you could have an API like
trait Error {
type Ok;
type Error;
fn ok(ok: Self::Ok) -> Self;
fn err(err: Self::Error) -> Self;
}
impl<T, E> Error for Result<T, E> {
type Ok = T;
type Error = E;
fn ok(ok: T) -> Self { Result::Ok(ok) }
fn err(err: E) -> Self { Result::Err(err) }
}
fn index<E: Error<Ok = usize, Error = &'static str>>(arr:&[u8], x:u8) -> E {
for i in 0..arr.len() {
if arr[i] == x {return E::ok(i)}
}
E::err("no index found")
}
and then you can pass a Panic<T> or Unreachable<T> which panics / calls unreachable_unchecked in the error case
See, that's an API I can get behind
Still doesn't feel necessary, but at least it doesn't have all of the above issues
maybe rust should have 2 kinds of panic macros
"your fault" and "my fault" panics
indexing out of bounds is your fault, if i am Vec::index
but a vec having a capacity > len is my fault
(kinda)
set_len lets you break that
they are not strings
edit: they are numbers (likely u8) that can be printed as the error, every throw funcs has this
it affects the _uncheked functions because you can use function%() instead, that is literally a lot better, you can choose which type of error do you want
what should i do if i fail?
close the app and warn me
or
return customized error
or
do nothing that error don't exists
its literally the better shit
how would these errors contain values?
i dont understand
if they are 256+ possible errors (unlikely af) they are just u16 instead u8
or better: trait error as you want, because it can have metadata (example: where is the error in X array) and decide if you want to panic or get error
Ah, it generates a thiserror-style enum. Still no. We should not encourage returning errors as just string enums, oftentimes they can have useful contents
These are all already possible. Handle the error with .unwrap(), .map_err()?, and .unwrap_unchecked() respectively
None of these needs magical syntax.
contain values
Enums can contain things. How will your errors do that?
enum CustomParseError {
InvalidByte{at: usize, byte: u8},
SomeOtherError(Foo),
}
Or, a practical example, try_send on channels, which contains the value you sent if it fails
Or Box::downcast
yeah. im not sure if it's even feasible really, and i wouldnt want to see it if it significantly complicated things
but if it was doable, it'd be cool
oh here's something fun i'd love to see: the ability to set the layout of types
for example, syntax negotiable, ```rs
struct Value(u64);
specialize Option<Value> {
None(value) if value.0 == 16
Some(value)
}
scary
keep behaviour separate from data
This is about data
Looks to me like an odd way to have manual niches for types,
such that Value(16) isn't valid, just like 0 isn't for NonZero* integer types.
yeah, effectively that
String keeping the name StrBuf
And more built-in string methods in std working with Cows
then do why unchecked function exist? lol
i changed a little my idea
fn index(arr:&[u8], x:u8) -> usize {
for i in 0..arr.len() {
if arr[i] == x {return i}
}
yeet 33333333u64; //just ignore this haven't sense
} //if yeet is empty return Option<>
fn main() {
let result = index#(&[5,56,7,8],3); //Result<u64, usize>
//now you cant just an specified error, because some functions need it
}
then there is no need to people make _unchecked functions, it will be automatic
but its too late to add it to rust
or maybe not?, just update the libraries.
How do you annotate the type of the error?
Unchecked functions exist for when you are absolutely sure to the extent that you're fine with your stuff going completely off the rails if it does ever turn out that the assumption does not hold
Which are rarely, but not never, needed to get all the way to the perf level you want
If (When?) UB happens, the program just stopping where it is with a fault is the least of your worries
But the potentially risky tools are there in the bottom shelf if you really need them
There exist multiple ways to do some things, get used to it (some of them are even specifically shorthands that intentionally exist along with their more general counterparts)
that's my favourite shelf ;-p
They're older.
The question was "if we can make any function de-facto unchecked by calling unreachable_unchecked on the result, why so other _unchecked functions exist?"
Also, convenience. Sometimes it's nice needing slightly fewer words to do a thing
This still has the absolutely crucial problen that no part of index's signature tells me it can fail. How am I supposed to know, looking at a function in some docs, that I'm meant to handle errors from it?
Though this is getting extremely close to just being current code + the "ok-wrapping" proposal of automatically wrapping stuff you return in Ok or Err depending on its type. That one might happen.
I also still don't think we need operators to abbreviate three perfectly good function calls, especially when the "good code" one is already an operator (?)
it panics by default if you want to know if it would panic you know you should try #
It panics by default
So we lose the entire point of results existing
No thanks.
The single best feature of results is that I can see them in signatures, and must handle them. A default of "oh yeah, hidden panics" is just... no.
Unchecked exceptions have never not been a mistake.
isn't result propouse do something when fail?
Yes, but there are many ways to do that.
Results advertise that you need to handle them
In your proposal, there is literally no way to know other than putting a # on a function and seeing if the compiler reacts to that. You lose the ability to just look at docs and know.
Also, if I'm understanding correctly, you also lose all of the helper methods (map, unwrap_or, and_then, map_err, ok, etc), and lose match too
So you'd be replacing self-documenting code and versatile and powerful error handling with unchecked exceptions, by far the worst form of error handling (ignoring what C has)
I wish there was a way to know, statically, that something cannot panic. But I figure that would be hell to actually track
you'd have to include it in the function signature, or as an attribute
which.. eee
you could alternatively have a "does panic" attribute instead
which is akin to java's throws ExceptionType but minus the type part
idk. what are the advantages?
It would probably be trivial to always propagate something that tracks whether something could panic (similar to non-const or async, anything that calls something that might panic, might also panic), but having anything meaningful would be difficult, because this doesn't account for something that never panics despite calling possibly-panicking functions.
I see the advantage as similar to the advantage of returning result instead of panicking: you know when there's the possibility of an error, (and ideally the cause is documented)
But a scheme like the ones I described seems like it would always be either useless (too many false positives) or impossible to track
Seems more the realm of dynamic types and theorem provers tbh
I would love to see thin references to DSTs... somehow (I have no idea how this would work, but I want it to work)
Am I hallucinating or was there an attribute #[thin] or something that you could put on traits?
But it would only work for local
I could swear there was an attribute that did that but now I can't find any reference to it, I must have literally hallucinated the docs page about it
i mean, thin dst refs can't Just Work
since you need some way of prevent oob accesses
maybe a trait
unsafe trait ThinDst {
fn size(&self) -> usize;
}
I swear I remember a docs page about thin dyn trait repr that only works if the trait is local and not inplementable outside the crate
I'm actually going insane at this point
which now means that miri asking "is this pointer access in-bounds" requires arbitrary code execution
which is uh
Not Great
with a slice it can just read off the length field
but if you want to support a FFI compatible &CStr
Good Luck
but that's something we do want
The problem with an implicit could-panic analysis is the massive semver hazard
Except for compiler bugs, the body of a function shouldn't effect its interface
laughs in Send/Sync leaking behind impl Trait
Hmm, I noticed that as part of an async bug, didn't know it happened normally as well
?play warn=true
fn sync() -> impl Sized {
42
}
fn not_sync() -> impl Sized {
std::cell::Cell::new(42)
}
fn assert_sync<T: Sync>(_x: T){
dbg!(std::any::type_name::<T>());
}
fn main() {
assert_sync(sync());
assert_sync(not_sync());
}
error[E0277]: `Cell<i32>` cannot be shared between threads safely
--> src/main.rs:15:15
|
5 | fn not_sync() -> impl Sized {
| ---------- within this `impl Sized`
...
15 | assert_sync(not_sync());
| ----------- ^^^^^^^^^^ `Cell<i32>` cannot be shared between threads safely
| |
| required by a bound introduced by this call
|
= help: within `impl Sized`, the trait `Sync` is not implemented for `Cell<i32>`
note: required because it appears within the type `impl Sized`
--> src/main.rs:5:18
|
5 | fn not_sync() -> impl Sized {
| ^^^^^^^^^^
note: required by a bound in `assert_sync`
--> src/main.rs:9:19
|
9 | fn assert_sync<T: Sync>(_x: T){
| ^^^^ required by this bound in `assert_sync`
For more information about this error, try `rustc --explain E0277`.
https://github.com/rust-lang/rust/issues/96865 <- the bug I ran into with generic async functions and associated types
but why returning result if you will unwrap()? only you know if you gonna handle it
unwrap is almost always the wrong thing to do with a result
⚠️ completely arbitrary made up numbers ahead
i'd say it's a bit like this
80% using ? (maybe with a map_err or anyhow or whatever to give context)
5% reporting the error
5% recovering from the error
3% igoring the error
2% unwrap
0% unwrap_unchecked
grepping through rustc source, there's 3414 )?s, and 2477 ).unwraps
soooo
unwraps are fine if you know it won't happen
like, if it shouldn't happen, don't bubble it up
shouldn't as in "there's a bug in my code if it happens"
It's worth mentioning unwrap is itself a "proper error mechanism" in rustc
It ICEs.
lot's of those unwraps are option unwraps, like on next() or last()
and about 500 are .unwrap_* so not true unwraps
unwrap is neat for quick and dirty dev. ? loses the traceback, whereas unwrap doesn't. And all other options are more work than.
there are crates that solve this, so you end up doing something like my_fallible_function().backtrace()?;, but one needs to search for those in the first place; and me personally, i've searched, found, and now forgot already.
Oh and you need to fiddle with function return type for ? to work properly
what i'm saying is that "proper" error handling is more work than .unwrap()
And this brings us to anyhow and its context
with anyhow it's barely more work to get these nice context traces
There's a lot of cases where you can guarantee that your option will hold a value, and in those cases it's perfectly fine to unwrap, but that's usually impossible to know with many Results, since those usually have more complicated error states
Even if you will unwrap, other people might do other things, like use a fallback
The nice thing about Result is that it's "just" a value, which either contains the expected return, or an error
Also if you're making a library it's not true that you know you will unwrap anyway, because you know nothing of the use cases
mutex.lock().unwrap()?
almost
I think it might be neat to see a version of Rust with constness by default, like we have today with immutability by default; const would be opt-out rather than opt-in. Functions could be marked as non-const with !const fn, and FFI functions would be implicitly marked as !const. That way, when you do const FOO: Type = // ..., it would check all the possible call paths of the expression, and if any of them aren't const, then it would throw a compiler error something like: ^ this call is indirectly !const. Alternatively, a compile error could be thrown when a function that calls another !const function isn't marked with !const itself, however that would be cumbersome.
Isn't the problem with that the semver hazard, where changing your function can accidentally make it implicitly !const?
Oh, hmmm, you're right, I didn't consider that
If you want a rigorous !const, it would probably look something like Haskell's IO monad
i'd be lying if I said I had haskell experience, lol
i like expect more than unwrap
even if its guaranteed it won't ever be None/Err, it is self-documenting on why i'm assuming that.