Last active 1 year, 6 months ago


Last active 1 year, 7 months ago


Last active 1 year, 11 months ago


Last active 2 years ago

#24 Newtype structs and related silliness 1 year, 14 days ago

Comment by ~araspik on ~icefox/garnet

I think that specifying the ABI for structs and tuples would be good, regardless of whether you decide to use a function for it in the end. About other languages not supporting the data ABI: Any language that can use the C ABI for function calling also allows using the C ABI for data structures, and so (assuming you're using a simple ABI very similar to C) there is no case where an external language would support the new() function without also supporting the ABI-based solution. As such, you don't need to generate new functions at all.

#7 Thoughts on matches/tree-walking (and data in general) 1 year, 14 days ago

Comment by ~araspik on ~icefox/garnet

The benefit of requiring this at compile-time is that you don't need to generate and save the VALUES array in program memory - unless, of course, you're providing RTTI and want to include enum values in it (although IMO there's no point in doing so as you can't use an enum without an understanding of what it does, which means that you already know the enum in your code i.e. at compile-time).

#17 Thoughts on type metaprogramming 1 year, 16 days ago

Comment by ~araspik on ~icefox/garnet

Ooh - we seem to have arrived at a misunderstanding. Consider the following example:

fn foo1(T: type);
fn foo2(T: type, sized: Sized for T);
fn foo3(T: type, sized: Sized for T, pod: POD for T);

While Sized for T is a struct in its own right, it does not represent T (in that it cannot be substituted for T directly in code), but provides additional information. As such, Trait1 for Trait2 for T isn't providing implementations of Trait1 and Trait2 for T, but the much weirder case of the implementation of a trait upon the implementation of a trait upon a type. Traits for each generic type, as required by a function, are passed separately as individual parameters. There's still a lot of boilerplate involved, but there's no ordering issue. There is also (still) the issue of multiple implementations of a single trait for a single type. For some traits (particularly where specialization is concerned), multiple implementations makes perfect sense, and is not really an issue (except that code has to pick a single implementation) - but for traits like Sized, we want a unique implementation. One option is to not regulate it, and to simply state that Bad Things™ will happen if multiple conflicting implementations are created, but this is quite against the spirit of safety of Rust. Another option is to use some sort of special annotation or attribute, and get the compiler to check uniqueness, which I guess can work.

Actually, I just realised that the 'unique implementation' concept may have significance for tied types in general, not just with tied types as traits. I'll need to think about that.

You've hinted at a second issue - negative constraints. This entire system assumes that traits are meant as an opt-in feature, that code can only depend upon a type implementing a trait, not upon a type not implementing a trait. I don't even know where to begin with allowing for them.

Reading about linear typing on Wikipedia, it looks like linear (and affine) typing is simply Rust's ownership and borrowing rules. What do you mean by the term?

I'm happy to provide discussion - I've been aiming at the same space between Rust and C with my own experiments, and so it's really great to see work by the others in this area.

#24 Newtype structs and related silliness 1 year, 16 days ago

Comment by ~araspik on ~icefox/garnet

The issue with public stub functions is that they have to be output, for the sake of external (foreign language) code where it can't be inlined. Some sort of replacement keyword for one-element tuples (e.g. newtype T and newtype pub T) may resolve the issue without resorting to the Feels Bad solution - I've always had a deep distaste for newtype X = Y.

I do suggest following in Rust's conservative path of making everything private by default. Consider it from an API guarantee standpoint: if users are allowed to access the internal stuff of library code by default, they are of course free to build upon it, and so assume that it is guaranteed to be provided even in later versions. If privacy is opt-in, then you just add a lot of boilerplate for library authors and add cognitive load because they have to include it every single time. With privacy opt-out, you ensure that only the necessary stuff is made public and thus included under API guarantees.

#7 Thoughts on matches/tree-walking (and data in general) 1 year, 16 days ago

Comment by ~araspik on ~icefox/garnet

Under that assumption, your code would have to work with a static foreach kind of construct, so that iteration over the enumeration variants is achieved. There (IMO) isn't a better way to express this.

The two use cases you've highlighted need to be separated, as they're used in typically quite different contexts. I think Zig pulls this off nicely with enum and a union of an enum, but the syntax needs to be re-evaluated because it involves quite a bit of boilerplate (in particular, all the variants need to be named twice). With Rust, I think a basic, consistent ABI for aggregate types should have been specified a long time ago, probably based on (or the same as) the SysV ABI - that would significantly aid inter-language operation. As a rule, the less implementation-defined stuff, the better.

#17 Thoughts on type metaprogramming 1 year, 18 days ago

Comment by ~araspik on ~icefox/garnet

One very cool idea I've found, inspired by this, is of having 'tied types', i.e. types whose each value is tied to some other value. For example, an Index structure may be tied to Vecs, so that every value of type Index is 'tied' to a value of type Vec. This allows not only for compile-time-proven elision of runtime checking, but also for implementing traits!

Consider a structure Sized (which contains size: usize, i.e. the size in bytes) tied to values of type type (i.e. types). You would have an instantiation of Sized for e.g. i32 where size = 4. This neatly provides for run-time and compile-time generics, as if you want information about a type (such as its size), you simply request an instantiation of a tied structure providing that information (i.e. Sized) which is tied to that type. For example:

struct Sized for type { size: usize };
fn alloc(T: type, sized: Sized for T) -> Box<T> {
    // Allocate space of size `sized.size` and return it wrapped in a `Box`

Such a function could be inlined at compile-time when the arguments are known, making it essentially just a typesafe malloc, or used at run-time with unknown types with the help of RTTI. It's just very neat, and it can be used to implement all the 'props' you've listed as simple zero-sized types. Also, you could provide multiple traits for different kinds of sizing (compile-time known, which includes the length as a field, run-time known, which includes a function to calculate it, and unknown, which has no extra info). The only reason Sized doesn't provide the length in Rust is because that information is implicitly provided everywhere (e.g. with dyn objects it's stored as an additional field).

There are some unresolved problems with tied types as traits, however: they allow for multiple different instantiations, and so may provide conflicting information (e.g. two different Sized values are constructed for i32 which say size = 4 and size = 2) - this can be overcome with Rust's orphan rules, so that only the module where Sized is defined can construct new values for it; they add some boilerplate in the source code; the compiler would need to track the tied value around to ensure that it still corresponds; and there are complex interactions with (im)mutably borrowing the tied value.

Just some food for thought.

#24 Newtype structs and related silliness 1 year, 18 days ago

Comment by ~araspik on ~icefox/garnet

I think that the Rust way of implementing newtypes - as single-element tuples - neatly solves the problem of generating constructors and destructors as they are just the tuple pack/unpack operations.

However, the single-element tuple model is not intuitive. It may help to provide a keyword, such as inner, so that y.inner or Foo.Inner (or whatever bikeshedding you want) are intuitive, instead of Foo_unwrap or the like which makes less sense. Constructing can be a simple Foo() function, although it may be easier to make that magic syntax (similar to e.g. tuple creation {a, b}), because adding a new essentially stub function that could get output in your machine code is a waste.

#7 Thoughts on matches/tree-walking (and data in general) 1 year, 18 days ago

Comment by ~araspik on ~icefox/garnet

The envisioned code you've provided here makes one fundamental assumption: that enumeration values are consecutive integers from 0 to something. I think that's why there's no direct shortcut available in Rust: that assumption can't be taken for granted, as you can specify custom values that may not be in order. Writing it out manually also enforces that you associate the right variant to the right function.

Another option you have is to use a compile-time for-loop over the available variants, deciding which function to use by indexing a compile-time list (or iterating over a zip pairing the variants to the functions, if that's your thing). In D, at least, this would be implemented with something like static foreach (although D doesn't have sum types).

#20 Allow non-gemini schemes 1 year, 7 months ago

on ~sircmpwn/gmni


#20 Allow non-gemini schemes 1 year, 7 months ago

Comment by ~araspik on ~sircmpwn/gmni

Huh, it's not segfaulting for me anymore.

But even then, we can support other schemes through Gemini. Perhaps for gmlnm it could be supported through a special visit-with-other-scheme command? For gmni it could just be an option.