RFC: Removing `as` (a WIP)

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Niko Matsakis
The `as` operator is annoying for a number of reasons:

- the precedence is non-obvious and hard to get right (3 + 4 as uint)
- it introduces a weird ambiguity around type parameters (`3 as foo::<T>`)

Recently Go's syntax was proposed as a remedy (`3.(int)`), but rejected
because it looked weird (which it does).

But it occurs to me that we don't really use `as` for much.  In fact, we
use it for three things: converting between numeric forms, creating
boxed ifaces, and converting C-like enums to their integer values.  I
have good alternative forms for the first two, not sure about the third.

### Converting between numeric forms

We can simply create some impls for the appropriate conversions.  For
example:

```
impl conv_methods for int {
     fn uint() -> uint { sys::int_to_uint(self) }
     fn u32() -> u32 { unsafe::reinterpret_cast(self) }
     fn float() -> float { ... }
}
```

These have the advantage of clear precedence and they're more attractive
to boot (at least to me).  Examples:

- `3.uint() + 4u`
- `(3+4).uint()`

### Creating boxed ifaces

I've started to find it a bit odd to use `as` to create a boxed iface,
since `as` looks like a "bitcast" (in LLVM terms) but it's actually
performing allocation.  It's also quite repetitive, especially if the
iface has type parameters.  Therefore, I propose we simply use the
`iface` keyword to indicate "create a boxed iface".  So, instead of this:

     foo as my_iface::<T1,T2>

we would write

     iface(foo)

The required iface and type parameters would typically be inferred from
context, but if you wanted to make it fully explicit, you would do:

     iface::<my_iface<T1,T2>>(foo)

This purposefully resembles a method call but of course `iface` is a
keyword so it is not, in fact, a method call but rather a special form.

*An aside:* I originally thought to use the name of the iface as the
constructor for a boxed iface (e.g., `my_iface(foo)`) but I eventually
rejected it because if we move to a system where ifaces can be allocated
not only in `@` boxes but also on the stack and so forth, the `iface`
keyword should be easily extensible to this scenario, whereas
`my_iface(foo)` is not.  This is true regardless of whether we use
"suffixed types" (e.g., `iface@(foo)`, `iface~(foo)`) or move to a
prefix-with-dynamically-sized-types system (e.g., `@iface(foo)`,
`~iface(foo)`).  Neither case can be gracefully handled by a named
constructor function unless these functions are actually treated
specially by the system.

### Converting C-like enums to their discriminant

Actually, I don't have a good idea what to do here.  The compiler could
of course automatically define the relevant methods on C-like enums
(`int`, `uint` and so on) but that seems hokey.

Thoughts?


Niko


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Benjamin Striegel
> 3.uint() + 4u

Does this parse correctly, or would you need to do (3).uint(), like in
Javascript?

On Wed, May 9, 2012 at 11:32 PM, Niko Matsakis <niko at alum.mit.edu> wrote:

> The `as` operator is annoying for a number of reasons:
>
> - the precedence is non-obvious and hard to get right (3 + 4 as uint)
> - it introduces a weird ambiguity around type parameters (`3 as foo::<T>`)
>
> Recently Go's syntax was proposed as a remedy (`3.(int)`), but rejected
> because it looked weird (which it does).
>
> But it occurs to me that we don't really use `as` for much.  In fact, we
> use it for three things: converting between numeric forms, creating boxed
> ifaces, and converting C-like enums to their integer values.  I have good
> alternative forms for the first two, not sure about the third.
>
> ### Converting between numeric forms
>
> We can simply create some impls for the appropriate conversions.  For
> example:
>
> ```
> impl conv_methods for int {
>    fn uint() -> uint { sys::int_to_uint(self) }
>    fn u32() -> u32 { unsafe::reinterpret_cast(self) }
>    fn float() -> float { ... }
> }
> ```
>
> These have the advantage of clear precedence and they're more attractive
> to boot (at least to me).  Examples:
>
> - `3.uint() + 4u`
> - `(3+4).uint()`
>
> ### Creating boxed ifaces
>
> I've started to find it a bit odd to use `as` to create a boxed iface,
> since `as` looks like a "bitcast" (in LLVM terms) but it's actually
> performing allocation.  It's also quite repetitive, especially if the iface
> has type parameters.  Therefore, I propose we simply use the `iface`
> keyword to indicate "create a boxed iface".  So, instead of this:
>
>    foo as my_iface::<T1,T2>
>
> we would write
>
>    iface(foo)
>
> The required iface and type parameters would typically be inferred from
> context, but if you wanted to make it fully explicit, you would do:
>
>    iface::<my_iface<T1,T2>>(foo)
>
> This purposefully resembles a method call but of course `iface` is a
> keyword so it is not, in fact, a method call but rather a special form.
>
> *An aside:* I originally thought to use the name of the iface as the
> constructor for a boxed iface (e.g., `my_iface(foo)`) but I eventually
> rejected it because if we move to a system where ifaces can be allocated
> not only in `@` boxes but also on the stack and so forth, the `iface`
> keyword should be easily extensible to this scenario, whereas
> `my_iface(foo)` is not.  This is true regardless of whether we use
> "suffixed types" (e.g., `iface@(foo)`, `iface~(foo)`) or move to a
> prefix-with-dynamically-sized-**types system (e.g., `@iface(foo)`,
> `~iface(foo)`).  Neither case can be gracefully handled by a named
> constructor function unless these functions are actually treated specially
> by the system.
>
> ### Converting C-like enums to their discriminant
>
> Actually, I don't have a good idea what to do here.  The compiler could of
> course automatically define the relevant methods on C-like enums (`int`,
> `uint` and so on) but that seems hokey.
>
> Thoughts?
>
>
> Niko
>
> ______________________________**_________________
> Rust-dev mailing list
> Rust-dev at mozilla.org
> https://mail.mozilla.org/**listinfo/rust-dev<https://mail.mozilla.org/listinfo/rust-dev>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20120510/65547e83/attachment.html>

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Niko Matsakis
On 5/10/12 5:20 AM, Benjamin Striegel wrote:
> > 3.uint() + 4u
>
> Does this parse correctly, or would you need to do (3).uint(), like in
> Javascript?

So, 3.foo() parses fine, but I'm not sure how -3.foo() parses,
actually.  Probably as -(3.foo()).


Niko

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Graydon Hoare
In reply to this post by Niko Matsakis
On 09/05/2012 8:32 PM, Niko Matsakis wrote:
> The `as` operator is annoying for a number of reasons:
>
> - the precedence is non-obvious and hard to get right (3 + 4 as uint)
> - it introduces a weird ambiguity around type parameters (`3 as foo::<T>`)
>
> Recently Go's syntax was proposed as a remedy (`3.(int)`), but rejected
> because it looked weird (which it does).

I concur it'd be nice to get rid of the current form.

I'm a little nervous about what you're proposing here. I think enums can
probably be done with a sys::enum_value<T> intrinsic that fails to
compile on non-enums. Or something involving the reflection interface
I'm currently building. I don't think this part is a blocker.

The bigger concern I have is moving from an expression that's reasonably
a const-expr to what looks like a function call. A few cases stand out
(say "((0xff << 10) | 'z') as uint" or such) though possibly they can be
solved by cramming more temporary consts and/or type-literal suffixes in.

More generally it raises my concern about const-exprs as functions.
Iintrinsics probably already have this problem; we won't constant-fold
through them. It'll get particularly bad if we move (as you and I have
discussed) to using the operator overload system for all the normal
arithmetic forms; 'as' is then just a special case of "how do we get
constant folding through user-defined expressions"? In C++0x they added
"constexpr" as a qualifier for functions. I could imagine supporting
'const fn foo' in rust as a way of indicating a fn that can be used in a
const-expr, at least when applied to const-expr arguments.

(nb: even if const-folding through user expressions wind up
turing-complete, it's not the same as "turing complete type system", and
I'm much less concerned about it. We already support the concept of
invoking arbitrary code at compile time, due to the plan for syntax
extensions and attribute extensions. My objection to making the _type_
system turing complete is that the encoding ,"evaluation" model,
type-system-of-types and diagnostic facilities are very very crude, so
it's a bad place to enrich to that level.)

So uh .. in the specific case of 'as' I'm not deeply worried what it
changes to, if anything. But I think we need to look at constant folding
first before making a move on it.

Sorry if that seems like derailing your goal!

-Graydon

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Niko Matsakis
That's a good point (as in const expressions).

What do you think about the iface casting form?


Niko

On 5/10/12 7:57 AM, Graydon Hoare wrote:

> On 09/05/2012 8:32 PM, Niko Matsakis wrote:
>> The `as` operator is annoying for a number of reasons:
>>
>> - the precedence is non-obvious and hard to get right (3 + 4 as uint)
>> - it introduces a weird ambiguity around type parameters (`3 as
>> foo::<T>`)
>>
>> Recently Go's syntax was proposed as a remedy (`3.(int)`), but rejected
>> because it looked weird (which it does).
>
> I concur it'd be nice to get rid of the current form.
>
> I'm a little nervous about what you're proposing here. I think enums
> can probably be done with a sys::enum_value<T> intrinsic that fails to
> compile on non-enums. Or something involving the reflection interface
> I'm currently building. I don't think this part is a blocker.
>
> The bigger concern I have is moving from an expression that's
> reasonably a const-expr to what looks like a function call. A few
> cases stand out (say "((0xff << 10) | 'z') as uint" or such) though
> possibly they can be solved by cramming more temporary consts and/or
> type-literal suffixes in.
>
> More generally it raises my concern about const-exprs as functions.
> Iintrinsics probably already have this problem; we won't constant-fold
> through them. It'll get particularly bad if we move (as you and I have
> discussed) to using the operator overload system for all the normal
> arithmetic forms; 'as' is then just a special case of "how do we get
> constant folding through user-defined expressions"? In C++0x they
> added "constexpr" as a qualifier for functions. I could imagine
> supporting 'const fn foo' in rust as a way of indicating a fn that can
> be used in a const-expr, at least when applied to const-expr arguments.
>
> (nb: even if const-folding through user expressions wind up
> turing-complete, it's not the same as "turing complete type system",
> and I'm much less concerned about it. We already support the concept
> of invoking arbitrary code at compile time, due to the plan for syntax
> extensions and attribute extensions. My objection to making the _type_
> system turing complete is that the encoding ,"evaluation" model,
> type-system-of-types and diagnostic facilities are very very crude, so
> it's a bad place to enrich to that level.)
>
> So uh .. in the specific case of 'as' I'm not deeply worried what it
> changes to, if anything. But I think we need to look at constant
> folding first before making a move on it.
>
> Sorry if that seems like derailing your goal!
>
> -Graydon
> _______________________________________________
> Rust-dev mailing list
> Rust-dev at mozilla.org
> https://mail.mozilla.org/listinfo/rust-dev


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Gareth Smith
In reply to this post by Graydon Hoare
On 10/05/12 15:57, Graydon Hoare wrote:
>
> I'm a little nervous about what you're proposing here. I think enums
> can probably be done with a sys::enum_value<T> intrinsic that fails to
> compile on non-enums. Or something involving the reflection interface
> I'm currently building. I don't think this part is a blocker.

Just a thought - how about `sys::enum_value<T:c_enum>(x: T) -> uint`
where c_enum is some new kind given only to c-style enums?

Gareth

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Gareth Smith
In reply to this post by Graydon Hoare
On 10/05/12 15:57, Graydon Hoare wrote:
>
> I'm a little nervous about what you're proposing here. I think enums
> can probably be done with a sys::enum_value<T> intrinsic that fails to
> compile on non-enums. Or something involving the reflection interface
> I'm currently building. I don't think this part is a blocker.

Just a thought - how about `sys::enum_value<T:c_enum>(x: T) -> uint`
where c_enum is some new kind given only to c-style enums?

Gareth


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Const exprs (was: Re: RFC: Removing `as` (a WIP))

Graydon Hoare
In reply to this post by Graydon Hoare
Further derailing the thread. Sorry, casts really got me thinking and I
need to probe this issue a bit because a lot of near-term work depends
on it:

On 10/05/2012 7:57 AM, Graydon Hoare wrote:

> So uh .. in the specific case of 'as' I'm not deeply worried what it
> changes to, if anything. But I think we need to look at constant folding
> first before making a move on it.

Thought about this a bit more:

   - 'const fn' is, I think, overkill. Obliges the compiler to do a ton
     of additional work simulating runtime semantics, and gets the phases
     really mixed up, circular.

   - The scary case is integers sneaking into types, as in fixed-size
     arrays. I think the main case for this is actually C interop, where
     a syntax extension that reaches into clang, grabs C type-size
     information, and returns a literal is probably sufficient. I don't
     think it's necessary to generalize to "any constant expression can
     appear in a type". Which is good, because that would be a huge
     undertaking and very likely make the phases circular, to the point
     of total confusion.

   - Given that, sizeof/alignof/offsetof probably don't need to be
     const exprs. The other use-case for distinct integer-const exprs
     (as opposed to general const exprs) is in enum discriminant values,
     and I don't know exactly why they can't be pushed back to "some LLVM
     constant we'll get the actual value of later". I'm willing to find
     out, if someone can enlighten me!

   - Very little compiler-work is saved -- in fact I think it's more work
     -- to spill, emit a call to a visitor, and reload (and inline that),
     instead of just emitting integer-add or fp-mul. At least for types
     already well understood to the compiler, LLVM, and machine.

   - The primitive types already have privileged status in a few places,
     such as literal forms, presence in types (again, fixed vectors),
     and permitting multiple integer types for the RHS of << and [] and
     whatnot.

   - All this combines to make me think:

      - Integer, fp, raw pointer, region pointer, literal, addr-of,
        concatenation and indexing is an ok set of constant exprs.
        Evaluation happens in target mode, operands must themselves
        be constants, and we can do it in LLVM. It's just a line
        distinguishing "what we can define a 'const' as and have in
        read-only memory".

      - All such constant expressions (including literals, raw
        and region-pointer ops, fp and integer ops), have fixed
        compiler-implemented meaning. Anything more complex you want
        to do _at compile time_ you have to shell out to a syntax
        extension for. Maybe we provide a variable-precision fp
        calculator extension for fancy math constants.

      - All _nontrivial_ operations are routed through visitors, where
        "trivial" means that the operation _could_ have been a constant
        expr if only it had been applied to constant operands. IOW
        anything that boils down to 0 or 1 LLVM operation, memcmp or
        memmove is trivial. Anything touching the heap or a dynamic-sized
        value is nontrivial.

I understand this is a somewhat ragged line cut through the space of
constant-ness, but I think it can be defended in a principled way: the
compiler does what compilers are good at -- primitives of various sorts,
read-only memory slabs and addresses -- and the libraries do the rest.

This does mean that sizeof, alignof and offsetof, applied to rust types,
remain "runtime" values, even though the compiler implements them as
intrinsics and they wind up as emitted / inlined constants. Again, I
_think_ the main reason we'd want to have these functions evaluated much
earlier -- i.e. "before types" -- has to do with C interop, and can be
handled by a syntax extension that returns literals calculated by clang,
but I could be wrong. Input welcome.

(I'll paste this into the relevant bug, #2317, momentarily)

-Graydon

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Graydon Hoare
In reply to this post by Niko Matsakis
On 10/05/2012 10:13 AM, Niko Matsakis wrote:
> That's a good point (as in const expressions).
>
> What do you think about the iface casting form?

Fine with it. For runtime casts I'm fine with any such syntax. 13.uint()
or uint(13) or ptr::cast(foo) or anything like that.

Also worth noting though, concerning casts: we do a fair number of
raw-pointer casts in unsafe code, "foo as *c_void" and such. Possibly
solvable with a slightly more terse function like reinterpret_cast (eg.
ptr::cast), but again, it breaks const exprs that might otherwise work
via 'as'. Not sure how big a deal that is if we've given up on having
such values evaluated "really early" (pre-types). See previous message
on consts I just sent.

-Graydon

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Const exprs

Niko Matsakis
In reply to this post by Graydon Hoare
On 5/10/12 11:22 AM, Graydon Hoare wrote:
> Further derailing the thread. Sorry, casts really got me thinking and
> I need to probe this issue a bit because a lot of near-term work
> depends on it:

This all sounds pretty reasonable to me.  I think we should draw the
line somewhere simple for now and only extend it if/when it proves
necessary.


Niko

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RFC: Removing `as` (a WIP)

Graydon Hoare
In reply to this post by Gareth Smith
On 10/05/2012 10:56 AM, Gareth Smith wrote:

> Just a thought - how about `sys::enum_value<T:c_enum>(x: T) -> uint`
> where c_enum is some new kind given only to c-style enums?

Possible. Feels like overkill just to support the one function, could
just as easily have the compiler emit an error for the one case in question.

-Graydon


Loading...