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

How much complexity should we invest in deduced/inferred return types? #1008

Closed
chandlerc opened this issue Jan 5, 2022 · 12 comments
Closed
Labels
leads question A question for the leads team

Comments

@chandlerc
Copy link
Contributor

Inferred function return types look like the following:

fn Add(x: i32, y: i32) -> auto { return x + y; }

Here, rather than specify the return type, the type is inferred from the return statement's expression: x + y.

This was added in #826 as a very minimal, simple feature. However, the restrictions placed on it may interact with other language features to be especially limiting.

Between #875 and some of the directions in #472, the inability to articulate a separate declaration and definition of such a function might make them unusable when nested in functions -- they already cannot be used with out-of-line definitions of nested functions.

C++ provides a way to allow declaring such functions -- until defined, their return type is in some ways incomplete / unknown. This introduces a certain amount of complexity but would allow out-of-line definitions of such functions, breaking some cycles, etc. This was left as an alternative not (at the time) considered in #826 when it went in.

If we don't want to add that complexity, and if we resolve #472 in a way that means inferred function return types won't work for nested functions, I think we should consider whether inferred function return types are truly carrying their weight. While they will remain a very simple feature, they'll have even more places where they interact poorly with the rest of the language (beyond virtual, interfaces, etc).

To help motivate that consideration, its also important to suggest the place where inferring the return type is especially well motivated: code that returns a type that may be especially difficult to name. For example, consider the C++ code:

auto BuildLambda(int a, int b) {
  return [a, b](int c) { return a + b + c; };
}

This kind of function returning a lambda is especially hard to write without an inferred return type. It's unclear the extent to which this code will come up in Carbon, but it maybe worth thinking about these kinds of functions whet making a decision here.

I think there are basically three directions we could choose between here:

  1. Make inferred function return types work as well as we can, adding some complexity to the language where needed.
  2. Keep them as they are, super simple but may be very limited in how and where they can be used.
  3. Remove them completely.
@josh11b
Copy link
Contributor

josh11b commented Jan 5, 2022

Rust has a feature called "impl Trait" for this use case. Beyond convenience, they do use it for some types that they can't otherwise name, such as the type of closures or the result of map or filter. The differences with Rust's feature are:

  • Instead of just auto, an interface is specified.
  • Instead of an actual type being deduced from the return expression in the body of the function, the return type is made generic. Callers can only use function declared in the specified interface.

Swift is considering a similar feature called "reverse generics".

Being able to return a type generically so that the actual type is only determined at codegen time has other uses, such as being able to define a function that returns a logger with different implementations in different binary builds.

@geoffromer
Copy link
Contributor

For example, consider the C++ code:

auto BuildLambda(int a, int b) {
  return [a, b](int c) { return a + b + c; };
}

I notice that this example (unintentionally?) uses a deduced return type in two different places: the return type of BuildLambda, and the return type of the lambda itself. I think this highlights two different ways that deduced return types can be useful: they let us define functions whose return types are difficult or impossible to spell explicitly, and they let us omit some boilerplate from lambdas. I'm not sure how important the first point will be for Carbon, but given how infamously verbose C++ lambdas are to begin with, it would be really unfortunate if Carbon's lambdas were even worse.

This isn't just a matter of aesthetics or point-scoring in language comparisons. Carbon's going to need a migration story for C++ code that uses macros for lazy/conditional argument evaluation (e.g. assert or LOG_IF(rare_condition) << expensive_expression). An extremely concise lambda syntax seems like one of the best options, but a mandatory return type would be a dealbreaker for that purpose (imagine trying to tell people that every usage of assert needs to contain -> bool, in addition to whatever lambda introducer we come up with).

@github-actions
Copy link

github-actions bot commented Apr 7, 2022

We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time.
This issue is labeled inactive because the last activity was over 90 days ago.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Apr 7, 2022
@chandlerc chandlerc removed the inactive Issues and PRs which have been inactive for at least 90 days. label Apr 7, 2022
@chandlerc
Copy link
Contributor Author

I agree with @geoffromer's comment.

Regarding "reverse generics" or similar -- I think that's a good thing to consider orthogonally here, and it seems well motivated. It also doesn't really suffer from the problem here (the trait provides the return type). This is more like having a a template T:! Type "reverse generic" return type that has to somehow be "dependent" and then materialize later.

I think I would suggest solution (2) from above: keep things as they are, super simple, but addresses the critical needs such as those raised by @geoffromer.

@chandlerc
Copy link
Contributor Author

I should clarify - the other reason I suggest just keeping the status quo is that I don't think this is urgent and we should just work with the current design a while before spending more time trying to evaluate any other options deeply.

@zygoloid
Copy link
Contributor

zygoloid commented Apr 8, 2022

I'm not sure exactly what keeping the status quo means. Given that we decided that member functions defined inline behave as if rewritten to a declaration plus a definition, so member functions always have a separate declaration and definition, would this mean that we can't use deduced return types for member functions at all?

@chandlerc
Copy link
Contributor Author

I'm not sure exactly what keeping the status quo means. Given that we decided that member functions defined inline behave as if rewritten to a declaration plus a definition, so member functions always have a separate declaration and definition, would this mean that we can't use deduced return types for member functions at all?

That is my understanding.

@github-actions
Copy link

github-actions bot commented Jul 9, 2022

We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time.
This issue is labeled inactive because the last activity was over 90 days ago.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Jul 9, 2022
@jonmeow jonmeow added the leads question A question for the leads team label Aug 10, 2022
@github-actions github-actions bot removed the inactive Issues and PRs which have been inactive for at least 90 days. label Aug 11, 2022
@github-actions
Copy link

github-actions bot commented Nov 9, 2022

We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time.
This issue is labeled inactive because the last activity was over 90 days ago.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Nov 9, 2022
@josh11b josh11b removed the inactive Issues and PRs which have been inactive for at least 90 days. label Nov 9, 2022
@josh11b
Copy link
Contributor

josh11b commented Nov 9, 2022

It seems like a tentative answer was proposed but never accepted? Can we resolve this question. In summary:

  • We are generally going with approach (2).
  • Member functions cannot use auto return type.

The one thing we might want to say, beyond this, is that we might want to support auto with multiple return paths, using the CommonType mechanism created in proposal #911 .

@chandlerc
Copy link
Contributor Author

The one thing we might want to say, beyond this, is that we might want to support auto with multiple return paths, using the CommonType mechanism created in proposal #911 .

I don't know that we need to say definitely "yes" or "no" to that here -- I think that can wait for someone to be motivated to write a proposal?

@chandlerc
Copy link
Contributor Author

While not thrilling, the status quo of (2) is the best thing we have for now and the leads are OK with that. We can look at proposals to use a common-type approach whenever someone writes one, and if needed revisit this with more data and a plan for how to do better than this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
leads question A question for the leads team
Projects
None yet
Development

No branches or pull requests

5 participants