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 |
> 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> > An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20120510/65547e83/attachment.html> |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
Free forum by Nabble | Edit this page |