Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking issue for experiments around coercions, generics, and Copy type ergonomics #44619

Closed
aturon opened this issue Sep 15, 2017 · 17 comments
Closed
Labels
A-type-system Area: Type system C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-postpone This issue / PR is in PFCP or FCP with a disposition to postpone it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. WG-traits Working group: Traits, https://internals.rust-lang.org/t/announcing-traits-working-group/6804

Comments

@aturon
Copy link
Member

aturon commented Sep 15, 2017

This issue tracks experimental work, overseen by the lang team, on several interrelated topics:

  • Improving the ergonomics around Copy types and references (i.e. "imagine never having to write &0 or let z = &u * &(&(&u.square() + &(&A * &u)) + &one); again)
  • Improving the consistency of method dispatch for operators with standard dispatch.
  • Improving the ergonomics around using owned values where references are expected.
  • Improving the interaction between generics and coercions.
  • Improving the interaction between function-passing code and coercions.
  • Helping to avoid accidental copies of large Copy types.
  • Potentially a very limited form of auto-Clone.

None of these bullet items should be taken as signifying a decision, but rather a desire to experiment in a particularly tricky design space.

Ongoing experiments

Relevant previous discussions

This tracking issue is a good place to continue discussion, at least initially; we may ultimately want to break out threads on internals to help hash out preliminary design thoughts.

After an experimental period, the expectation is that whatever designs are deemed sufficiently plausible are turned into fresh RFCs, and then go through the standard process.

@aturon aturon added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Sep 15, 2017
@aturon aturon added E-needs-mentor WG-traits Working group: Traits, https://internals.rust-lang.org/t/announcing-traits-working-group/6804 labels Sep 17, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC label Sep 18, 2017
@SergioBenitez
Copy link
Contributor

SergioBenitez commented Sep 28, 2017

While trying to generalize a concept in Rocket, I ran into issues when coercions I expected to happen automatically did not. I'll illustrate with examples.

In the code below, the Handler trait is implemented for all types that implement the function trait Fn(&str) -> &str (note the implicit HRTB). Among these types, of course, is for<'r> fn(&'r str) -> &'r str. The program below typechecks:

trait Handler { }

impl<F> Handler for F where F: Fn(&str) -> &str { }

fn is_handler<H: Handler>(_: H) {  }

fn dyn_handler(_: &str) -> &str { "hi" }

is_handler(dyn_handler);

On the other hand, none of the is_handler calls below typecheck:

fn static_handler(_: &str) -> &'static str { "hi" }

is_handler(static_handler);

is_handler(|_| "hi");

is_handler(|x| x);

In each of these cases, however, a manual coercion can be applied that results in the call typechecking:

is_handler(static_handler as for<'r> fn(&'r str) -> &'r str);

is_handler((|_| "hi") as for<'r> fn(&'r str) -> &'r str);

is_handler((|x| x) as for<'r> fn(&'r str) -> &'r str);

I expected that the coercions applied manually above would be applied automatically by Rust. In particular, I expected the 'static lifetime in return types to coerce to a generic lifetime.

But note that similar coercions are automatically applied when a function trait bound is enjoined without an accompanying impl search (re: @eddyb). The following program typechecks without any manual coercions:

fn mk_handler<F: Fn(&str) -> &str>(f: F) -> F { f }

is_handler(mk_handler(|_| "hi"));

is_handler(mk_handler(|x| x));

This implies that there already exists machinery to automatically perform these types of coercions in certain situations. I'd like to see these particular automatic coercions be applied during impl search as well, at least in the case of function types.

P.S: There are also cases where a manual coercion doesn't result in a typechecked call while an automatic coercion does:

// does not typecheck
is_handler((|x: &str| x) as for<'r> fn(&'r str) -> &'r str);

// typechecks
is_handler(mk_handler(|x: &str| x));

And cases were neither works where it would likely be expected to:

// does not typecheck
is_handler((|x: &str| -> &str { x }) as for<'r> fn(&'r str) -> &'r str);

// does not typecheck
is_handler(mk_handler(|x: &str| -> &str { x }));

@arielb1
Copy link
Contributor

arielb1 commented Sep 28, 2017

I think what's going here is just that coercions, like everything else in Rust, don't trigger based on the set of pending trait obligations.

I am assuming you are talking about this example:

is_handler(|_| "hi"); //~ ERROR
is_handler(mk_handler(|_| "hi")); // ok

There are no coercions in either of these examples - the second one successfully uses the [closure]: Fn(&str) -> &str impl. The reason the first example doesn't work is because you only get 1 chance to trigger higher-ranked inference (I don't think this is going to get any better - this area is full of undecidable cases and is basically heuristic), and a _: Handler bound isn't enough to trigger it (while a _: Fn(&str) -> &str is).

However, some specific examples are different, non-coercion problems:

is_handler((|x: &str| x) as for<'r> fn(&'r str) -> &'r str);

That is just #38714, which is being fixed

is_handler(mk_handler(|x: &str| -> &str { x }));

This also looks like some sort of screwiness, we might look into it.

@SergioBenitez
Copy link
Contributor

I think what's going here is just that coercions, like everything else in Rust, don't trigger based on the set of pending trait obligations.

This appears to be true sometimes and not other times, but either way, my point is that (at least for function types) coercions should be trigged based on the set of pending trait obligations. For an instance of when these coercions do presently trigger, see the example towards the middle of my comment. I've reproduced it below:

fn mk_handler<F: Fn(&str) -> &str>(f: F) -> F { f }

is_handler(mk_handler(|_| "hi"));

is_handler(mk_handler(|x| x));

@arielb1
Copy link
Contributor

arielb1 commented Sep 28, 2017

This appears to be true sometimes and not other times, but either way, my point is that (at least for function types) coercions should be trigged based on the set of pending trait obligations.

Actually there are no coercions in any of these examples (except when explicitly triggered via casts), just heuristic HRT inference.

@SergioBenitez
Copy link
Contributor

SergioBenitez commented Sep 28, 2017

Actually there are no coercions in any of these examples (except when explicitly triggered via casts), just heuristic HRT inference.

Sorry, you're right; I was indeed referring to inference with respect to the closures. To rephrase, I meant to exhibit that the inferred type for |_| "hi" is not closure<'r> &'r str -> &'static str but instead closure<'r> &'r str -> &'r str. If that same type had been inferred in the earlier examples, then the closure-based code would be expected to typecheck.

@shepmaster
Copy link
Member

For my own future searchability: this issue would address (certain cases of) the Eye of Sauron.

@H2CO3
Copy link

H2CO3 commented Mar 26, 2018

I would also be nice to add a lint for autoref, not only auto-deref. Autoref can be dangerous too (when exact addresses matter, e.g. in FFI and memory-mapped I/O) — and for me, "dangerous" is far worse than "degrades performance" as a first approximation. Personally I would never want to use either the autoref or the autoderef feature, and I'd like the compiler to scream at me if it still ever happens by accident.

@dobkeratops
Copy link

dobkeratops commented Nov 23, 2018

interesting issue,

my wish is for auto-borrows, (coming from C++ operator behaviour). I'm having to bounce between methods, operators, copy types with a number of conflicting draws (with C++ giving the case I actually want). it's not just that "it's that way in c++" - we had the ability to write anything (unchecked move or borrow) in C; whilst people complain about C++ references, sane operators was actually their driving use case.

personally I would hope it's possible to get the best of both worlds perhaps with a #[autoborrow] preference on a type or perhaps enabling it in a module (I think I'd go (more) insane if you had to enable it per function or per parameter though)

As I see it moves or borrows are different preferences based on situations; I've never wanted maths to consume via moves, wheras control structures for threads & inserting items into collections are a clear move use-case.

Also without overloading it should be easier to determine what to do from the function name (the compiler does seem to know the alternative in its error messages alot of the time)

explicit mutable borrows are ok as you want to see the side effects.

Besides that there's an intuition/readability aspect - mixing &'s and +'s hurts alot (and people must likely move back & forth between C++/rust.. I can't afford to throw away my fluency with the former) because you've learned to expect those operators to have certain meaning in positions, when combined like that it just looks like an error (& is associated with bitwise arithmetic or generating addresses when not, + near addresses is associated with pointer arithmetic.. , it's too much ). it was less of a pain to revert to named functions ("a.vadd(&b)").

I would have thought it would be far more sensible all round to keep the expectation of operators the same as C++ and resort to named functions for anything else (e.g. + is sometimes abused for concatenation which has different semantics to arithmetic.. better to use a named function there if you don't want to go the custom-operator route)

Also originally I thought this was more about the operators but I realise now (from a previous 'bounce' in my sourcebase) that it's about the lack of auto-borrow generally (autoborrow exists for the receiver and is fine there). you can apply a concept like 'linear interpolation' to some quite weighty objects, but it would be nice to keep the same interface as for vectors.. it's a sliding scale.

It creates distress relying on unstated semantics r.e making maths-types 'copy' ("hope the compiler will turn it into a reference..") which also narrows the use cases. (having delved into AI i want to keep my graphics maths general enough for n-d vector, which don't want to be 'copy'). I want to just implement operators for borrows, and have those work, rather than have to implement &T op T T op &T etc etc.

Since I discovered Rust 5 years ago I originally went for named methods for maths , because the borrows were less obtrusive (and not all the overload permutations could be expressed) ; I was happy to wait and see how things evolved but unfortunately I think operators went the wrong way.. but it should be possible to fix with a backwards compatible preference

@dobkeratops
Copy link

to throw an idea out there,
what would it look like if fn foo(&self,...) could auto-borrow its arguments ,but fn foo(self,...) and free functions could not? (borrowed receiver = preference for borrowed arguments?)

@steveklabnik
Copy link
Member

So, visiting this for triage; none of this ended moving forward, right? What do we do about this ticket?

@camelid
Copy link
Member

camelid commented Sep 30, 2020

Hello from triage! Could someone summarize what has or hasn't been decided upon?

@joshtriplett
Copy link
Member

We discussed this in the @rust-lang/lang meeting today. We'd still like to see many of these things happen, and we'd welcome project proposals, but we need to narrow the scope to specific proposals. In the meantime:

@rfcbot postpone

@rfcbot
Copy link

rfcbot commented Oct 5, 2020

Team member @joshtriplett has proposed to postpone this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-postpone This issue / PR is in PFCP or FCP with a disposition to postpone it. labels Oct 5, 2020
@scottmcm
Copy link
Member

scottmcm commented Oct 5, 2020

I'd definitely like to see experimentation in these areas -- I still like the "discarding ownership" idea -- but realistically it doesn't seem like anything is happening right now so this issue isn't providing value, so ☑.

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Oct 12, 2020
@rfcbot
Copy link

rfcbot commented Oct 12, 2020

🔔 This is now entering its final comment period, as per the review above. 🔔

@camelid camelid added the A-type-system Area: Type system label Oct 12, 2020
@nikomatsakis
Copy link
Contributor

Like @scottmcm I am happy to close this issue, but I am still in favor of most of the ideas here. I think the right way forward at this point is probably experimenting with implementation. I don't know how much bandwidth there is for such mentoring, but I'd encourage folks to reach out. I would also say that filing lang team proposals for specific ideas here would be great, although I think the response will largely be "we're interested but it's going to require some experimentation!"

Mark-Simulacrum added a commit to Mark-Simulacrum/lang-team that referenced this issue Oct 20, 2020
This draws mostly on readily available links in
rust-lang/rust#44619.
Mark-Simulacrum added a commit to Mark-Simulacrum/lang-team that referenced this issue Oct 20, 2020
This draws mostly on readily available links in
rust-lang/rust#44619.
@Mark-Simulacrum
Copy link
Member

Closing in favor of rust-lang/lang-team#62 and rust-lang/lang-team#63, as discussed in a recent language team meeting -- but if someone disagrees with that, please feel free to reopen.

Mark-Simulacrum added a commit to Mark-Simulacrum/lang-team that referenced this issue Oct 21, 2020
This draws mostly on readily available links in
rust-lang/rust#44619.
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Oct 22, 2020
@spastorino spastorino removed the to-announce Announce this issue on triage meeting label Oct 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-type-system Area: Type system C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-postpone This issue / PR is in PFCP or FCP with a disposition to postpone it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. WG-traits Working group: Traits, https://internals.rust-lang.org/t/announcing-traits-working-group/6804
Projects
None yet
Development

No branches or pull requests