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 should types customize, and potentially expose, their value representation? #2990

Open
chandlerc opened this issue Jul 15, 2023 · 3 comments
Labels
leads question A question for the leads team

Comments

@chandlerc
Copy link
Contributor

chandlerc commented Jul 15, 2023

Summary of issue:

Proposal #2006 adds value expressions to Carbon, one key feature is the ability to have customized representations for values of a given type.

This leads to a few related questions:

  1. How should a type request such a customization?
  2. Should the customization be exposed to generic code?
    • If so, how should that exposed type be modeled? The obvious way is as an associated type of an interface -- what should the interface and type be named, and how should they relate to (1)?

Details:

Background

Proposal #2006 codifies Carbon's expression categories, including value expressions. Beyond just classifying expressions, these in turn are modeled directly in Carbon's function parameters as value parameters. These parameters work much like const & parameters in C++ do, but have the advantage that they are allowed to be represented in a more efficient way when desired -- such as a copy in a machine register.

There are two value representations that are expected to be very common for a type T:

  • const T: the value representation is a (constant) copy, maybe in a machine register like an i32 might be.
  • const T*: the value representation is a (constant) pointer, avoiding a potentially expensive copy, much like a C++ const & might.

There is a more rare third option: a type may provide an entirely custom value representation. This can be especially useful when there are well established or particularly efficient "view" types, for example C++'s std::string_view. A detailed example from the proposal can be found rendered nicely here: https://github.com/chandlerc/carbon-lang/blob/pointers2/docs/design/values.md#value-representation-and-customization

Question (1): How should types customize this?

One option is for types to implement an interface:

class IntLike {
  // ...

  impl as ValueRep where .Rep = const IntLike;
}

class BigAndSlowToCopy {
  // ...

  impl as ValueRep where .Rep = const BigAndSlowToCopy*;
}

class String {
  // ...

  impl as ValueRep where .Rep = StringView;
}

However, this has some challenges. First, we would like there to be a default for the majority of types, but that default to be different for different types -- the compiler should pick a "good" default. We could try to encode this as a template impl of the interface, but it isn't clear how or when a type stops being able to provide its own. Immediately after the type is fully defined we might want to understand how to work with a value expression.

A second issue that came up in informal discussions was that this is a very significant behavior to signal through an implementation of an interface, and we might want a more distinct syntax to highlight this.

We could imagine some custom syntax, and in fact #2006 currently uses a placeholder syntax:

class IntLike {
  // ...

  value_rep = const Self;
}

class BigAndSlowToCopy {
  // ...

  value_rep = const Self*;
}

class String {
  // ...

  value_rep = StringView;
}

This placeholder syntax intentionally isn't great. This issue is the place to suggest other, better syntaxes that we might use.

We had a situation with some similarity but also some important differences with destructors and chose dedicated syntax.

Question (2): should this customization be exposed generically?

Once we have some answer to (1), we can imagine wanting to access that in a generic context. Should that be supported?

If so, the most obvious approach would be an interface that is always implemented for types. If (1) uses an interface, we're kinda done. But if (1) uses explicit syntax, how should that manifest in an interface implementation with an associated type?

One concern with doing this is that this customization isn't very generic in any sense. What you can do with a T varies in non-generic ways between a value representation of const T* and some unrelated U. The semantics could be copy-based with a const T rep, or by-reference with a const T*, or even a mixture.

Any other information that you want to share?

This leads question is extracted from a discussion in #2006 here: https://github.com/carbon-language/carbon-lang/pull/2006/files#r1244089593

@chandlerc chandlerc added the leads question A question for the leads team label Jul 15, 2023
@josh11b
Copy link
Contributor

josh11b commented Jul 15, 2023

On question (2): one possibility is: there might be one interface that is used for types with a custom value representation, that has a Convert method and extends another interface (ValueRepresentation) that has an associated type with the value representation type. The Convert method doesn't make sense for all types, but the associated type does.

@GauravN123
Copy link

@josh11b the Convert method makes sense for all types

@josh11b
Copy link
Contributor

josh11b commented Aug 25, 2023

I think the syntax problems here are going to be very similar to what we are going to face with defining "move" for a class, and we should have a consistent solution to both problems (along with maybe destructors). I recommend first figuring out what parameters we need to specify the move behavior of a class, and then consider syntactic options for customizing these class behaviors together.

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

3 participants