--- title: "Problems and designs" tags: ["T-lang", "design-meeting", "minutes"] date: 2024-09-09 url: https://hackmd.io/jD1Pmf6wQZGVWd6VOxooEw --- # Meetup 2024: Problems and Designs Preparation: Brainstorm a list of long-standing problems and design ideas you are interested in discussing. Feel free to add them in the sections below ahead of the meeting. ## Long-standing problems Abstract: Discuss some of the biggest problems that have dogged Rustaceans since the beginning of time, and what it would take to solve them. We'll hold open the possibility of multiple directions, noting our individual preferences but not hashing them out here (those can happen in unconference sessions or future design meetings). ### My onerous problem here ### Brainstorm - "picking the impl of a generic interface" -- dependency injection - The "only one possible impl" inference rule, and the inference breakage it causes. Should we consider changing that in a future Rust edition? - Note for this: some surprising things, like `arr[some_usize_expr]`, may depend on this - Should figure out what it'd take to try some experiments here - Why can't I use `into` to convert from `usize` to `u64`, infallibly? Can we fix this without a massive infrastructure? - Lint group for "I'm a popular library, please warn me about convenience/future-proofing tradeoffs and help me favor future-proofing" - Esteban Kuber is experimenting with possibilities in this area - Module system usability improvements: making `mod` unnecessary/optional, inferring modules from the filesystem, etc. - Note that David Barsky from rust-analyzer will be at RustConf if we want to cross-check anything with r-a. - Keyword arguments (both "whether" and "how"), or more generally, what do we *want* people to do for complex function invocations? - Needing to write `map.get(&x)` or `map[&x]` (was: Auto-ref in `[]`, `.get()`, `.get_mut()`) - Problem is: I get a compiler error telling me to add `&` every time I do a map lookup; doesn't add value to my code (especially when my key is Copy) - Disambiguated resolution for new items in std that conflict with ecosystem - Supertrait item shadowing - New decision process and its automation - We've had draft implementations for a long time, we should review/test them and figure out what it'd take to deploy them - Could we auto-start decisions on e.g. T-lang RFCs? - Could we let anyone in the project start a T-lang decision? - More general `dyn Trait` - Our current object-safe method rules mean that certain trait designs simply do not work for dyn Trait. (e.g. trait methods that return associated types of the trait.) - What can we do to generalize `dyn Trait` (or what features/changes can we make to associated items) to enable trait designers to make traits that continue to have great static reasoning capabilities *without* walling off use of `dyn Trait`? - dyn Trait usability "writ large" - what exactly is that makes `dyn` so annoying and how can we make it better? - starting proposal: - it would be nice if `fn foo(x: impl Fn())` could be changed to `fn foo(x: dyn Fn())` - unsized parameters in general! - Orphan rule / coherence checking - How can we make it easy to always be able to ship a crate XY that provides glue combining crate X and crate Y, rather than having to ship that as a feature in X or Y? This would help the ecosystem scale. - How do we balance the benefits of the orphan rule for scalability, with the *drawbacks* of the orphan rule for scalability - FRU that does what people expect - private fields and non-exhaustive specifically - Perhaps, more generally, chaining everywhere (tap crate etc) - stability on trait impls - visibility of trait impls - type inference fallbacks (indexing by u32 please!) - How do we feel about i32 fallback actually happening in "real" code? - integers and literals (`const FromLiteral`?) - How can I wrap an integer type without the ergonomics being terrible? - cc `4.hours()` and `4_hours` and `hours!4` and friends - Built-in bignums (and other "mathematical" types e.g. rationals, reals, arbitrary-precision decimals, sets, sequences?) - Numeric traits / numeric tower. Could we extract all the common methods from uN/iN into traits? Is this 100% library or is there some language support needed here? - semver-ness of `repr` and such - `as` 💥☢️ :tada: - Conversion methods and traits for specific functionality of `as`. Methods to convert between signed/unsigned. Method for truncation. - struct fluidity and whether builders/named-parameters are essential - `?`, await, and friends in iterator combinators / "Tennet's Correspondence Principle" - support for more "Aspect-Oriented Programming" like patterns. E.g. traits where multiple implementations are allowed, with rules stating that e.g. *all* matching trait methods are run (and the results are conjoined in some natural manner, e.g. and'ing or or'ing booleans...) - there are other patterns from AOP, like "before" and "after" advice methods... Are these considered Rust-y, or anti-Rust-y, accoding to our design philosophies? - "context passing" -- getting values from one place to another, e.g., Debug impl that needs context - State machine patterns and other uses of guaranteed TCE - Making proc macros more ergonomic to write - Some way to express multi-crate patterns with less overhead - Most notably, some way to test multi-crate patterns in the playground, but it goes further than that. - Support for the common "context" pattern, and accessing that context via callbacks from crates external to your own code. - E.g. "contexts and capabilities". - Missing pieces in const-ification - E.g., how to get `for` loops in `const` contexts. We need a way to make `<_ as Iterator>::next()` `const`. - This seems more pressing now that we're stabilizing `&mut` in `const` contexts. - move types (can't make a reference) ### (Niko's pick #1) Dependency injection People need a way to pick their crypto provider. - Example: abstract over ring vs aws-lc - Externally implementable functions/statics/traits/etc ([RFC 3632](https://github.com/rust-lang/rfcs/pull/3632), [RFC 3635](https://github.com/rust-lang/rfcs/pull/3635))? Other examples: serde, async, malloc, panic handler. * monomorphize w/r/t to the ultimate choice * build against a dynamic invocation * building against different values in different places * possibly subconstraints (e.g., specializing to subtraits, or to particular choices) who picks? * probably want intermediate crates to be able to choose but root crate to be able to override what's wrong with patching out a crate? * can't upload it to crates.io and reuse it * what about stdlib? also, very common. * care more about binary size so I want to re-use the qsort I'm already forced to link in, for instance * and float parsing, and `task::spawn`, and the allocator, and ... * can't check whether you meet the interface ("duck typing") There's tie-in here to: - Contexts and capabilities. - I.e., dynamic scoping in a statically-typed way. - Externally-implementable functions/statics. - Adversarial interoperability vs cooperative interoperability Possible things you could do: - Lightweight: Start with fancier cargo `[patch]`, e.g. `ring = aws-lc` - Contexts - Module-level generics (upcoming vibe check) - Cargo global / mutually exclusive features Also connected to revamping cargo features. - Used for a lot of things today that we don't have a better alternative to. - Would be nice if we could build a base version then apply cfg after the fact. Make monomorphization substantially more powerful? Motivate by missing functionality, not "where Platform: is prettier". But it would solve the caching problem. ### Scott's pick - semver & visibility/stability on trait impls - ties in with.. `#[repr(c)]` as a trait - ties in with orphan rule - Josh: We should separate out having stability markers + visibility-without-multiple-impls and having *multiple impls*. The latter is harder and complicated (and ties in with the orphan rule), but the former is much easier. "Resolve as you would have, then check stability/visibility." Can start with non-orphan rule subset. Don't change where you can write the impls, only change who can see them. - Niko: Seems like an obvious first place, but need to land the new trait solver. Good example of a cross-team ask (to T-types). Stability of impls: Every step of the logic chain would need to be stable. - might also be able to do this like const stability, where const-stable can't call const-unstable things. Pending compiler stuff. Needs an RFC, to elaborate on the motivation for things we might want to do with this kind of stability control. Motivation examples: - `impl(crate) ReprC for MyStruct` - can use it internally, not part of your interface - say as `impl(crate) std::fmt::Debug for ...` - Already use visibility for things, already have traits to describe types, now combining them - Compiler-provided `impl(crate) Reflection for _` - and enum discriminants and other things - "where Param: enum + repr(T), T: BasicIntegerType" (This doesn't solve the "new method in std breaks existing code" problem; need something else for that.) We could punt on that problem for now, because today we don't *have* unstable impls, and insta-stable impls already break stable, so it's not *worse* if we have non-insta-stable impls that *sometimes* still break stable. Next step: Write an RFC. ### Josh's pick - WANT to talk about "why can't I just use `into` for `usize` -> `u64`" - BUT I think it's more important to talk about the "only one impl" rule and inference fallout How could we address that? - Shorter-term: - lint that says you are relying on this, maybe as part of a broader lint group - `#[warn(upgrade_fragile)]` - see also I never want the `i32` fallback in "real" code - lint against non-generic usage of `.as_ref()`? - Long-term: - New edition to drop and mitigate this Sketch: You can say: `Vec` will only ever `impl Index<usize> {}` and rely on that. Haskell has a way called "functional deps" or fundeps which is more extreme than this, at the trait level. Though we could add a trait attribute (e.g. one on `Index`) that disallows you from having more than one impl per self type. Another direction is picking a "fallback impl" if it's ambiguous. Also: How do we get to a place where we can index with u8 and not only usize? niko's proposal * 07.07: * deny-by-default lint for specific known mistakes, like `T-to-&T` via `.as_ref()` * `T`-to-`T` via `From`/`Into` (when no cfg and trait aliases involved) * "we're allowed to break you so don't do this" * 👍 * 10: * allow-by-default lint for when "known trouble traits" (Into, AsRef, etc) rely on inference rules * 20: * Allow impls to declare that they are the only impls, have an allow-by-default lint for relying on "only one impl"-based inference for anything else * Needs RFC * 30: * Upgrade to warn-by-default * 40: * Upgrade to error-by-default * 50: * Hard error in 2027 * GOTO 10 Next step: Write an RFC that defines the only-one-impl attribute and the warning. > Side conversation: i32 fallback > - Is the correct fallback bignum like in python? > - Can we just warn about every single use of `i32` fallback? > - Scott would like the fallback of `4` to be `integer{min=4, max=4}` > - side note: these kind of types will be important for static verification --nikomatsakis > - Pattern types? Have a fixed int width etc. > - `impl const FromIntLiteral` > - Numeric trait tower > - When looking at the one-impl rule, if part of it is about literals, then maybe we could have `impl Index<IntegerLiteral> for Foo` and not actually need to use the one-impl rule for those cases. ### TC's pick - Missing pieces in const-ification. - E.g., how to get `for` loops in `const` contexts. We need a way to make `<_ as Iterator>::next()` `const`. - This seems more pressing now that we're stabilizing `&mut` in `const` contexts. TC: usability of `const` is pretty bad because we don't have traits in iterators, need iterator `next` etc to be const-able tmandry: const modifier on traits has definitely come up joshtriplett: can do special cases for well-known traits or we can solve the general problem. (shades of keyword generics proposal.) Seems related to "conditional send" (`T: Send => Foo<T>: Send`). I wonder if this will come up or be needed in const. tmandry: there is experimental syntax in nightly with `~const` TC: was on a call where people talked about "associated effects", seems like it had a nice analogy to Rust and associated types, results in a better, more analogous syntax than many of the proposals I've seen. One example that really drove it home was a fold operation (higher-ranked function being passed in); naturally it has its own effects. Return type of that fold operation needs to be a combination of the effects of the fold function itself and the trait that it's in. Looked at that and thought "how would you do that with syntaxes we've been considering..." and realized it just didn't quite work. The more I thought about the HR cases the more I liked this analogy. nikomatsakis: Unconference topic? I have thoughts on effect generics. nikomatsakis: What would be the value of special trait? scottmcm: In stdlib we stopped "randomly const'ing" things unless it "just works". nikomatsakis: So addressing this issue would expand the scope of things in the stdlib that are const scottmcm: Right, we stopped accepting PRs changing `for` to `while`. That particular thing comes up a *ton*. Example: <https://github.com/rust-lang/rust/blob/38e3a5771cefc9362976a605549f8b04d5707311/library/core/src/slice/ascii.rs#L316-L324> (though for code size reasons that might actually stay a while let until we can optimize iterators better in llvm) nikomatsakis: The special case might not be specific traits but traits that meet specific criteria of simplicity. scottmcm: Stabilizing mut-refs will also help; anything with closures though is going to be a big complication. Need const closures. Next step: Unconf topic. ### Felix's pick - dyn trait usability - BIG Q: what exactly is that makes `dyn` so annoying, and how can we make it better? - sub Q: how to make more traits+methods+impl combo's object-safe? (E.g. enable returning some kind of associated types via dynamic dispatch.) - sub Q: how to encourage more use of dyn trait rather than pre-commmitment to impl trait? NM: This is OK: `fn foo(x: impl Fn() -> u32)` But this is annoying: `fn foo(x: &dyn Fn() -> u32)`, `foo(&|| ...)` nikomatsakis: I'd like to enumerate some code examples and be able to say "if we can make these work...dyn will be less annoying..." and I'd nominate this as one of them. scottmcm: Unsized parameters? Can we do them? (Scoping out unsized locals because mir people don't like them.) NM: I'm opposed \[to unsized locals] now that they can't go in a future. joshtriplett: related to owned references, right? `&own`, a thing with a destructor and a lifetime. NM: `&own` is basically `dyn*` (fyi)-- `dyn*` is a pointer + a vtable, and one thing in there is a destructor *stack of things to be brought up:* nikomatsakis: one interesting thing is that this interacts with allocation pnkfelix: what is design space for dynamic dispatched method that returns an associated type? (E.g. would the trait definition (or impl Trait<..>) have to declare a trait bound on the associated type, and construct an associated vtable that gets injected at the right points?) * NM: This is strongly related to issues with async methods in dyn Futures ```rust trait Foo { type X: Bar; // trait Foo is "dyn safe" iff Bar is "dyn safe" fn foo(&self, x: impl Bar) -> Self::X; } trait Bar { } // point is that, to some extent, you could convert `Foo` to `dyn` // if `Bar` can be made `dyn` like so... impl<T> Foo for DynAdapter<T> where T: Bar, { type X = Box<dyn Bar>; fn foo(&self, x: impl Bar) -> Box<dyn Bar> { // -------- // This case is a bit different. T::foo(self, x) } } ``` scottmcm: I keep mentioning <https://nickb.dev/blog/the-dark-side-of-inlining-and-monomorphization/> to people about how actually `dyn` is great, and how, no, you don't want to mono everything all the time. ```rust trait Foo { // value of the unnamed associated type would be `dyn* Future` async fn a(&self); fn b(&mut self) -> impl Future; // probably you do want C type C: Future; fn c(&self) -> Self::C; } trait IntoIterator { // probably don't want `dyn* Sized` type Item; // probably do want `Iter` to be `dyn* Iterator<Item = Item>` type Iter: Iterator<Item = Item>; } fn foo(x: dyn IntoIterator<Item = u32>) { let n = x.into_iter().next().unwrap(); // ok, all dyn calls } ``` pnkfelix/niko: Specifically regarding the `fn foo(&self, x: impl Bar) -> ...` case above: the key problem is that *any* method with generic type (e.g. `fn foo<X: Bar>(&self, x: X) -> ...`, for which the above `impl Bar` is a short-hand ) is immediately *not object safe*, and regardless of what we do regarding returning an associated type, *that* problem will not be immediately resolved. niko: But I think we might be able to handle the special case of `fn foo(&self, x: impl Bar)` so that *it* is dyn-safe (without tackling aribtrary generic methods). ### Tyler's pick - int as newtype - as and integer conversions - auto-ref ## Design ideas (vibe checks) Abstract: Bring 1-2 Rust bucket list items and get reactions from the team. Come prepared with a 1-2 paragraph description that states the problem and rough solution as clearly as you can (short code samples are encouraged). Soft time limit of 6 minutes, hard time limit of 12 minutes per item (depending on how many we have to get through). Everyone is entitled to bring two ideas for discussion; more if we have time. Long-standing RFCs in FCP without boxes checked and without concerns may also be a good fit for vibe-check items. ### My wonderful idea here ### Brainstorm - Rename "object-safe" and family to "dyn-compatible", to make them more self-explanatory - Stability markers for libraries - Currently worked around with features - Externally implementable functions/statics/traits/etc ([RFC 3632](https://github.com/rust-lang/rfcs/pull/3632), [RFC 3635](https://github.com/rust-lang/rfcs/pull/3635)) - Distributed slices, and how we want to handle them in various linking scenarios - `cfg(accessible(...))` and `cfg((version(...))` - Can we get both of these over the finish line? - Supertrait item shadowing - if we stabilize this, is that enough to avoid a deluge of `cfg(version(...))` uses? - Is anything blocking [the RFC](https://github.com/rust-lang/rfcs/issues/3624) or does it just not have checkboxes yet? - Ergonomic ref-counting (if anything is blocking it) - Postfix macros (`expr.mac!(args)`) ([RFC 2442](https://github.com/rust-lang/rfcs/pull/2442)) - Extending format args to support `dotted.exprs` - Is this lang, libs-api, or both? - Do we want to go any further, or draw a firm stopping line? - Do we want a language (or library) mechanism for [tap](https://docs.rs/tap/latest/tap/)-style chaining? - Making map/and_then as easy as `foo?.bar?.baz` in Swift - Generalized "projection" somehow? - Should we establish/hire/pay a group of one or more people we can delegate small tasks to? - Goal: have a default way to handle things that need a few hours or a few days to complete. - Things from the problem-solving session that we thought were very close to the finish line - Small bits of design or similar iteration on something we care about seeing finished in a timely fashion - Simple experiments - Process automation - Leadership Council budget - Handling changes that draw community pile-ons / :-1:s - This has come up many times before. - `async<T>` for `impl Future<Output = T>`, and `gen<T>` for `impl Iterator<Item = T>` (and natural future extensions like `async gen<T>`) - How to benefit more from being built on LLVM. - "Leave nothing on the table." - `_` and `'_` in generic parameter lists: - `struct Foo<_> { x: impl Debug }` => `struct Foo<T: Debug> { x: T }` - `fn foo<_, I>(x: impl Iterator<Item = I>)` => `fn foo<x: Iterator<Item = I>, I>(x: X)` - `struct Cx<'_> { cx: &u32 }` - Generic parameters on modules - "applicative" functors with a twist - `_` in function argument lists for closures - `x.iter().map(_.bar()).collect()` ### Tyler's pick I hate writing `something.foo_that_returns_opt().map(|x| x.thing()).and_then(|x| x.foo)` `try { something.foo_that_returns_opt()?.thing().foo? }` - Making map/and_then as easy as `foo?.bar?.baz` in Swift - also `x ?? <default>` - is `try { foo?.bar?.baz }` ok? - will it infer option-ness? - might need .as_mut() in some places - e.g. `try { x.my_set.as_mut()?.insert(...) }` (which results in `Option<bool>`) or something? - `_` in function argument lists for closures - `x.iter().map(_.bar()).collect()` - `something.foo_that_returns_opt().map(_.thing()).and_then(_.foo)` - Josh: something like `_` has been proposed before, but how do you know how far the closure extends? - gotta make some rules... - Scott: C# has this; it's sometimes unclear to me where the `?`-ness ends. I actually really like sticking braces around it so you know where to get the option back. - `foo?.Bar.Should().NotBeNull()` // doesn't work because C# doesn't call those function - `(foo?.Bar).Should().NotBeNull()` // works because the parens are load-bearing Vibe check: Make `try` work here, but try to make it nice!! ### Felix's pick - Conjunctive traits (i.e. multiple impls for a trait: call them all and combine the results via some associative+commutative operator). See also the Aspect Oriented Programming notes above. - motivation: additive invariants for a type. Example: `impl Restriction for Username` Seems related to the ideas for relaxing the orphan rule - some of those ideas are "allow multiple impls and use just one (because they're all equivalent)", while this is "use them all". Related to distributed slices, could use distributed slices to implement this. Have a single impl that calls all the functions in a distributed slice. - e.g. all `#[test]` functions Niko: Seems like accumulating is the basic ability you need. Reducing is something you can do yourself. Tyler: Makes me think of accumulating template instantiations. Niko: We reserve the special power of monomorphization to ourselves, let's give it to libraries! ### Niko's pick - `_` and `'_` in generic parameter lists: - `struct Foo<_> { x: impl Debug }` => `struct Foo<T: Debug> { x: T }` - `fn foo<_, I>(x: impl Iterator<Item = I>)` => `fn foo<x: Iterator<Item = I>, I>(x: X)` - `struct Cx<'_> { cx: &u32 }` ```rust // brain initially thinks of this (which isn't valid) struct MyContext { x: impl Iterator<Item = u32> } // Rust forces me to write this: struct MyContext<I: Iterator<Item = u32>> { x: I } // but wouldn't it be nice to get close to 1st case this way: struct MyContext<_> { x: impl Iterator<Item = u32> } // and then that would analogously enable this ... fn foo<I: Iterator<Item = u32>>(x: I) // ... at last to turn into this: fn foo<_>(x: impl Iterator<Item = u32>) ``` ```rust struct Foo<'_> { x: &u32, // I wish I could just use this y: &u32, // all the same lifetime z: &u32, // all the same lifetime } // Plausible limitations: // You can either use `'_` or a set of named lifetimes but not both struct Foo<'_, 'a> // hmm ? ``` TC: I actually do want a TAIT in structs often, e.g. with `Future`. Niko: For this use case? ```rust async fn compute_something() {} struct Wrapper { f: compute_something() } ``` TC: Yes. Scott: Could we sometimes just have the name of the struct without giving it generic Josh: Would like to see a solution to this. Syntax idea: ```rust! struct Foo { x: impl Iterator<Item = u32>, } let x: Foo<x = MyIterator> ``` NM: I see. That could work I guess, just annoying. followups: - can I `impl Foo for impl Bar { ... }` ? NM: yes I want that a lot scottmcm: I guess this doesn't work since type parameters aren't nominal, but... ```rust struct Foo<T, ..> { x: X @ impl Iterator<Item = T>, y: Y @ impl Iterator<Item = T>, } ``` tmandry: Yeah I've been vaguely wanting something like this too. NM: don't hate it ### Scott's pick `unsafe` fields, since apparently I recently wrote a UAF in core by `derive(Clone)`. https://github.com/rust-lang/rfcs/pull/3458/ Questions: - Is it unsafe to read, write, both? - Is the scope individual fields or a whole struct NM: Please. Vibe: :+1: :100: tmandry: Don't see why not! We just have to make sure we actually hit the use cases correctly with whatever factoring we pick. joshtriplett: Something *like* RFC 3323 seems like a good way to be more specific if we ever want read restrictions scottmcm: But I want mut as the default joshtriplett: :+1: for `unsafe` doing the thing people want by default pnkfelix: There's a long thread on that RFC thread and https://github.com/rust-lang/rfcs/issues/381, ending with Ralf going from being opposed to unsafe fields to saying this RFC would resolve that issue. I would want to review that thread, but Ralf gives me more confidence. scottmcm: (Relevant history involving `Vec::set_len` and how people are confused why it's unsafe when it doesn't do anything unsafe.) ### Josh's pick - Rename "object-safe" and family to "dyn-capable" (or "dyn-compatible"), to make them more self-explanatory. TC: Regarding "safety" in particular, here are the kinds of safety we have: - Memory safety - Type safety - Thread safety - Cancellation safety - Unwind safety - Object safety - I/O safety - Others? According to Lamport, "a safety property is one which states that something will *not* happen" (emphasis in original, [Lamport 1977][]). He continues: > For example, the partial correctness of a single process program is a safety property. It states that if the program is started with the correct input, then it cannot stop if it does not produce the correct output. [Lamport 1977]: https://web.archive.org/web/20170921232628/http://www.cis.umassd.edu/~hxu/courses/cis481/references/Lamport-1977.pdf So the apologia of "safety" is that we're guaranteeing that bad things don't happen when accessing the vtable, and the requirements we impose on the traits are what are needed to ensure that. NM: :+1: to `dyn-X` for some value of `X`; in general I'm trying to stop saying trait object and start saying "dyn trait" TC: +1 to using "dyn" here in the name. Next step: Josh will file an issue and FCP it. https://github.com/rust-lang/lang-team/issues/286#issuecomment-2338905118 ### TC's pick Having traits with a ton of methods like `Iterator` is a problem when we start considering new traits like `Gen` or `AsyncGen` (or `ConstAsyncGen`, `AsyncGenFn`, etc.). The trouble is deciding whether to copy all of these around by hand, keep them in sync, not make mistakes of inconsistency, etc. It seems like traits are the answer to this, ironically. I'd like us to be able to have traits like `AndThen` or `FlatMap`. Then the trait contracts ensure that things stay consistent. It'd be interesting to think about the various limitations that make doing this a problem. The one that comes to mind is the interaction with closure types. I.e., some impls of `AndThen` can take a `FnOnce` as an argument, while some need `FnMut`. I.e., it calls for a kind of refinement. Felix: does this require specialization? E.g. today individual impls of Iterator can override the default methods that trait Iterator provides for e.g. `fn and_then`... Scott: Boats has a blog post somewhere or Twitter thread about why Monad doesn't work in Rust. TC: Not proposing that. Niko: Seems related to the fact that we can't factor our a supertrait from a subtrait today. Josh: Related: Marking a trait impl as `#[inherent]` -- we could make "inherent supertraits" that make it possible. NM: I'd rather do that for all supertraits, but can we jump back to "do we want to do this at all"? I'm not sure. What inconsistencies are we talking about? scottmcm: semver is interesting for foreign traits, but at least in the same crate yes please to "for all supertraits", niko. TM: Not planning to propose general monads but NM: is this roughly what it would look like...? ```rust impl AndThen { type Item; type AndThen<U>; fn and_then<U>(self, op: impl FnMut(Self::Item) -> U) -> Self::AndThen<U>; // something like this... } trait Iterator: AndThen<AndThen = std::iter::AndThen<...>> { } default impl<I: Iterator> AndThen for I { // ...? } ``` The goal is to ensure that the signatures and contracts are consistent between traits. TC: The canonical examples are: ```rust trait Map<T> { type Wrapped<U>; fn map<U, F>(self, f: F) -> Self::Wrapped<U> where F: FnMut(T) -> U; } trait Apply<T>: Map<T> { fn new(x: T) -> Self; fn apply<U, F>(self, f: Self::Wrapped<F>) -> Self::Wrapped<U> where F: FnMut(T) -> U; } trait AndThen<T>: Apply<T> { fn and_then<U, F>(self, f: F) -> Self::Wrapped<U> where F: FnMut(T) -> Self::Wrapped<U>; } ``` Scott: Related to being able to provide a default impl of your supertraits; we'd need that. NM: Specialization kind of had two parts, one was "specializing impls" (the harder part) and one was "specializing defaults on traits". This is the second half, being able to go beyond "one default defined in the trait" and towards "refining the default you get". ### Tyler's pick No more `&` for `[]`, `.get()`, `.get_mut()`, especially if `T: Copy` scottmcm: :100: for discarding ownership. Idea: Okay to pass ownership to something if the caller only wants a reference – as long as you actually run the destructor (if there is one) If you call ```rust foo(x) ``` but x is `T` and `foo` wants `&T` then we treat it like ```rust foo(&{x}) ``` so you pass a reference to a temporary that gets dropped in the same place it would have gotten dropped if the function had taken it by value. Felix: What are the failure modes? Dropping a mutex guard accidentally? (maybe an opt-in or opt-out would suffice to address this; i.e. the `T: Copy` constraint as written above...) Josh: Wouldn't quite match UFCS, since `x.foo()` wouldn't take ownership but `Foo::foo(x)` would. Don't think we care though. scottmcm: Doing it for an `&mut` parameter would be a footgun. That's more of a signal, where as `&` is more noise. Josh: What kind of signal are we losing? "This entire method doesn't need ownership, you should maybe change your method not to require ownership." How do we get that signal otherwise? Clippy lint flagging methods that don't seem to need ownership? NM: what about just for `T: Copy`? the mutex guard is giving me pause. tmandry: I think that's a reasonable conservative first step. ### Felix's pick - Isn't `Cow<'a, T>` very often the right thing to use, and yet no one uses it because it has second class status compared to `&T` or `Box<T>`. What's weird about Cow? - Cow hardcodes `ToOwned`, which means you can't - Cow into an Rc - Array into slice What if Cow were in core? That would decouple it from `ToOwned`, but has coherence issues. ```rust // in core enum Cow<'a, Borrowed, Owned> { .. } // various methods taking where Owned: Borrow<Borrowed> ``` Josh: Maybe the thing we want is `&own`. For callees that don't care about mutation, but want to allow for it to be owned or not. You could even have `&own mut` where you can mutate it but it might be your own temporary that goes away when you're done with it. Scott: Sure wish we could have figured out how to put string literals into String. We got so close. Felix: +1. Josh: :+1: NM: I rarely want the "copy-on-write" part, but I often wish I could return an owned value where a `&` is expected... TC: I used it for a zero-copy parser. NM notes in passing: Dada makes shared = Cow-without-writes :) tmandry: Relatedly, I would like `.use` to work with `Cow`. NM: :thinking_face: huh, not sure what that means :) ### TC's pick Postfix macros have been blocked on how to handle the `self`. It's far down my writing list, but I mean to propose "simplest postfix macros" that would take `$self:expr`. I want macros like `.repeat(..)` that would have evaluation control, but if we wanted to prevent that as a partial stabilization, we could lint against expansions that don't start by binding `$self`. JT: If we want to eventually give control over evaluation, I'm all for this design. If we feel we want postfix macros but DON'T want to give them control over evaluation, I don't think we should start with this "lint-like" design. scottmcm: `false.implies!(...)` is always true, iirc? ```rust foo .bar() .baz!() // how many times does `bar` get called? baz!(foo.bar()) // today you know foo.bar() may be called any number of times ``` Josh: "garden path sentences", don't want to have to re-consider what you've already read when you see a macro JT: my proposal was to have auto-ref work by doing something like `let foo = lvalue_ref $expr`.... so that you figure out the way it is going to get used, just like we do scottmcm: why not get a *value*? TC: Even then, you need a separate fragment specifier. It's not particular to postfix macros. If we want it, we should add it to all macros. scottmcm: reminder that `$self` is not actually reserved. vibes: conflicted. ### Tyler's pick `x.&`, `x.&mut`, `x.&raw const` Josh: Would like to see this. Relatedly: there was a proposal to use `^` for postfix *deref*, and `~` for navigate-without-dereference, like `mystruct~field^`). NM: this would let you make auto-ref less horrible... ```rust vec.&mut.push(22); ``` Vibe: Everyone likes the idea but nobody knows the syntax. tmandry: I used to think `.ref` was better but have come around to just `.` NM: don't hate it. Josh: `.&.field_name`, not `.&field_name`, right? Tyler: Yes. scottmcm: TBH I like the less-sigil-ness of `.ref`. NM: I just don't like `&raw` but that's orthogonal Josh: Any technical barriers? scottmcm: what about something that gives `unsafe<'a> &'a`? `.&`, `.&mut` -- nikomatsakis, TC, pnkfelix, joshtriplett `.ref`, `.ref mut` `.ref`, `.mut` -- scottmcm `v.mut.push(4)` // I admit this is pretty --nikomatsakis TC: There's a polarity question. ```rust ⊢ $ident: &T ---------------- ⊢ ref $ident: T ``` ```rust! let ref x = y.ref // has x = `&&y` ``` pnkfelix: Should it be accepted in patterns? What would it do? (I'm fine with it being rejected in pattern context.) Niko: There's a general thing about making prefix things work in postfix with `.`, in favor of `.&`. (Like UFCS) NM: I feel uncomfortable with `x.&` not working in patterns but more comfortable if there are two rules: * `&x` and `&pat` (symmetric) * `<prefix> <expr>` and `<expr>.<prefix>` ### Josh's pick - In type context, `async<T>` for `impl Future<Output = T>`, and `gen<T>` for `impl Iterator<Item = T>` (and natural future extensions like `async gen<T>`) (note: previously proposed with different syntax, this is a re-proposal with simpler-to-parse syntax.) TC: There are other proposals to which I'm more partial. E.g.: - `async -> u8` - `impl Future -> u8` We have to think about how this extends to, e.g.: - `async gen yields u8` FK: would these ever require input types? or always just output? TC: coroutines might have two generic types - `coro yields u8 -> u8` TM: both output types, though fn types do have inputs JT: I think `impl Future -> u8` will parse worse than `async<u8>` in places you might want it, e.g. ```rust fn something() -> impl Future -> u8 { async move { 22_u8 } } fn something() -> async<u8> { async move { 22_u8 } } ``` SM: but if that's a problem, then arguably it's also a problem for ```rust fn something() -> impl Fn() -> u8 { async move { 22_u8 } } ``` and thus there should be a solution that makes that too. TC: I'm hesitant to elide the impl, I think it's important for describing what they "mean" in the sense that it's an opaque type like other impl traits. JT: Not opposed to trying the idea of bare `Trait` meaning `impl Trait` over an edition, but part of the point of this syntax is to hide `impl`. NM: `impl Iterator<Item = u32>`, is there some way to "shorten" it? is that what "yields" is? TC: `impl Iterator yields u32` `impl Future -> u8` `impl MyTrait -> u8` ```rust trait MyTrait { #[Bikeshed(Output)] type Output; #[Bikeshed(Yields)] type Item; } ``` ```impl -> impl MyTrait yields u8; ``` `impl Add<u32> -> u32` NM: I'd like to be able to try some of these. Would want to port a medium codebase to it. / JT: +1, let's implement both and see how it goes Where "both" is...? - `async<T>` and `gen<T>` - `impl Future -> T` and `impl Future -> T` ? scottmcm: I guess we're stuck unable to use `fn foo() -> fn -> T` for a sugar like this, aren't we. I'd been hoping that `async` having this sugar would let use have a closure of the same. Vibe check: Uncertain. Let's play with it. ### Scott's pick So, `as`. NM: DOUBLE DOWN `x as as u32` `x unsafe as u32` `x yeet as u32` Josh: Add `.trunc()` (turbofishable), add `.to_signed()` and `.to_unsigned()`, then nuke `as`. scottmcm: some kind of `u32::wrapping_from(x)` seems like a first step. NM: Every time I try to replace `as` with something better I end up going back to `as` Problems: - No impls for `From<u32> for usize` or `From<usize> for u32` - Can't turbofish into or try_into. `.into::<u32>()` - TC: `Into`/`From` not const NM: my use case tends to be just "I have a usize that I converted to a u32 and now want to convert back" and there seems to be no nice way to do this, `.to_usize()` would be fine scottmcm: outside of `usize`, what happens? - how often do people need `as Box<dyn ...>`? - do we want a debug-checked version - float conversion - pointers are in the strict provenance APIs scottmcm: As an initial proposal, only allow `as` for things that are lossless. JT: How bad would it be to turbofish into? Can we TM: clearly `x.u32::from()` NM: what are you talking about MY EYES NM: ok maybe it's not so bad... `x.<Vec<T>>::from()` ??? Vibe check: We all want it, someone needs to do the annoying work. ### Niko's pick `T: Unsized` instead of `T: ?Sized` along with `T: DynSized` and friends as proposed in [my blog post][2024-04-23] [2024-04-23]: https://smallcultfollowing.com/babysteps/blog/2024/04/23/dynsized-unsized/ Not-very-old RFC: https://github.com/rust-lang/rfcs/pull/3396 Problem comment: https://github.com/rust-lang/rfcs/pull/3396#issuecomment-1728509626 ```rust trait Sized: DynSized { } trait DynSized: Unsized { } trait Unsized { } fn foo<T: Unsized>() { } ``` * AllSized as a possible synonym for Unsized ```rust struct Rc<T: AllSized> { } ``` Orthogonal considerations: * the `?` signals that this bound works in the "opposite way" of a normal bound * nikomatsakis is arguing that this is "ok" -- that special trait names are ok too * vibes range from apathetic to supportive * the name `Unsized` -- not ideal but maybe there's no better alternative * DynSized == should we call it MetaSized? so as not to confuse with dyn * vibes range from positive to meh * and I wanted `trait Foo: Unsized` * scottmcm is not sure about it * could start by requiring people to add `Unsized` when you have `T: Foo` for now * do we want to add alignment into the hierarchy? * maybes?? ```rust! trait Foo: Unsized { } trait Bar: Sized { } trait Baz { } T: Foo == no default for Sized T: Bar == no default but has explicit Sized T: Baz == default Sized T: Foo + Bar == T: Bar + Baz == ``` Vibe checks... JT: Yes please, just find the right names. `Unsized` sounds like asserting you're not sized. Would be fine with a name that implies that sizedness is *unknown*; until then, let's stick with `?Sized`? SM: Not sure about the `Foo: Unsized` thing. TC: If we're willing to say `T: ?Sized + DynSized`, that does unblock `DynSized`, and is actually what the current RFC says. Turns out -- not quite -- `?Sized` is really `DynSized` given that `size_of_val(`... - Niko: We could deprecate `size_of_val` and say it panics sometimes. TC: On naming... ```rust trait Foo {} fn f<T>(x: T) { // "T does not implement `Foo`." } ``` TC: Is there a general language feature here... bound that is implied for all type params? NM: Maybe within a module. Not sure if I like it. TC: If there were a syntax for expressing such a trait hierarchy yourself it would make more sense to people in the case of `Sized` as well. NM: that makes sense to me, even though I'm not sure I want to add it. --- XXX -- push this to an unconf :) Some recent thoughts on specialization. 1. I like `if impl T: Trait` as a construct for "implementation specialization" ... ```rust if impl X: Trait { // in here the compiler knows that `X: Trait` // checked at monomorphization time } ``` 2. But what about capabilities? I kind of like `T: ?Foo` to mean "I can test whether `Foo` is implemented" ```rust trait ``` ## Deferred conversations we will DEFINITELY finish tomorrow (unconf) - Specialization - Effects - String literals work with string type - `&own`, Cow, dyn - Module-level-generics and existential types oh my