11 ms·
Variadic generics, again
- BobbyJo 3y agoI cant think of a single real use case for variadic generics. When you're talking about functions operating on mixed, variable length, types, what meaningful logic can you actually apply, that doesn't fall into requiring the types to implement an interface anyway? When you're talking about struct/classes/etc, what do you need over and above defining a class/record/dataclass? It would seem to me, you're just skipping the part where you give your tuple a name, but is that even a good idea to do? To be clear, I did not find the examples in the post convincing, as they represent mostly wrappers/adapters for implementing or consuming arbitrary types, but there are no examples for how that might actually be useful downstream.
- oddlama 3y agoI can think of plenty real world usecases. Take for example a network interface that can send or receive predefined packet structs. Imagine each packet requires a size header. When you want to send multiple packets at once, you now want to optimize that and only write a single initial header to the interface, preceding the data. With variadic generics, you can enable a syntax like `interface.write_packets(packet1, packet2, packet3, /* ... */);` which writes the packets in the desired optimized way. It can internally construct a serializable data tuple from the variadic generics and add the correct header only to the beginning. Without variadic generics a similar syntax is only possible with macros, which means that it cannot be implemented as a reusable trait. > It would seem to me, you're just skipping the part where you give your tuple a name, but is that even a good idea to do? Sometimes you explicitly don't want to require naming the tuple for flexibility, like in the example above.
- paulddraper 3y ago!!! Consider having multiple (heterogenous) Promises and wanting to wait for all to complete, and return the results.
- norir 3y ago> I cant think of a single real use case for variadic generics. Even if you could think of one, I personally think the rust community does a poor job of weighing the tradeoff between supporting a niche feature and adding complexity to the language. Rust is already so huge that all but the most dedicated have little hope of understanding even half of the language features. I'm not sure I understand the incentive structure that makes it this way, but it is hard for me to see how rust avoids becoming the next c++ unless they learn how to say no. Honestly I believe it's already too late and that rust's best ideas will be adopted by a smaller language with better ergonomics, compiler performance and learnability that will eat its lunch.
- Georgelemental 3y agoThe status quo is that people who would like to use variadics but can't, instead settle for complicated ad-hoc macros. Those are far worse for "ergonomics, compiler performance and learnability."
- MrBuddyCasino 3y agoInstead of extending the language with ever more niche features, they could pull a Zig and make macros not suck.
- SkiFire13 3y agoZig's `comptime` is not a silver bullet though: - it is duck typed, which to be fair macros are also, but variadic generics would be type checked at the definition; - it interacts badly with traits, that is if you allow both querying for implemented traits and creating new trait `impl`s then this can create cycles in the execution of comptime blocks, meaning the compiler needs to compute a fixpoint or arbitrarily give an error.
- Georgelemental 3y agoAlso, Rust has lifetimes which are erased prior to monomorphisation—so specializing based on them is unsound. Any `comptime` equivalent in Rust would have to limit the access of `comptime` blocks to lifetime information in some way.
- jandrewrogers 3y agoI think the utility is strongly dependent on the kind of code you write and the sophistication of the metaprogramming your code will benefit from. Many C++ programmers made similar comments when it was introduced with C++11 and a few still maintain it has no real use case. However, for C++ library writers it was a game changer. In practice, they replace a lot of use cases for code generators. It took me a while to get into variadic generics, but now that I understand them I would feel crippled using a language without them. A lot of the value is to derive complex structs programmatically that may never be realized concretely but feed other metaprogramming algorithms that slice and dice them and make decisions based on the properties of the individual types. You don’t want to name them because (1) there may be hundreds from an unbounded set and (2) your may never need to reference them directly, so it saves a mountain of code and eliminates a massive amount of edge cases. At their best, you can use them to express insanely complex things with surprisingly little code. It is not the kind of feature you use profligately in a code base. But when you need them, you really need them. Most of what they can express in ordinary code would otherwise require complex code generators, compiler magic, or outrageous amounts of boilerplate.
- amluto 3y agoI used variadic templates to implement, drumroll please, a variant library that wasn’t as awful as what came before. Rust has enums, which are so much better it isn’t even funny.
- lmm 3y ago> When you're talking about functions operating on mixed, variable length, types, what meaningful logic can you actually apply, that doesn't fall into requiring the types to implement an interface anyway? Anything higher-order. At the most basic level you want to be able to take a bundle of values and a bundle of functions and apply the functions to the values, or take a bundle of functions and another bundle of functions and make a bundle of composed functions. You will probably complain that these examples are trivial, but if we don't have the syntax or vocabulary to handle the trivial cases then how can we even start to think about the more complex cases? > When you're talking about struct/classes/etc, what do you need over and above defining a class/record/dataclass? It would seem to me, you're just skipping the part where you give your tuple a name, but is that even a good idea to do? It is, if only because you want to be able to transform existing records generically rather than having to concretely write out every specialised possible version of your record. For example, think of writing a generic "patch" method for use in the "update" route of a CRUD webservice - something that lets you call it with an existing record (e.g. a user) and some representation of a few deeply nested fields of that record (e.g. the postal code that's on that user's address) and get an updated version of the record, without having to write out the type for that "patch" input longhand. This is a basic, trivial thing that people do in TypeScript every day, but you still can't even write a type for it in Rust, yet alone implement it.
- Georgelemental 3y agoFrom the post: > I think variadics aren’t necessarily useful for application developers, or people working on libraries with mostly concrete types (eg web libraries). But for people working on libraries with lots of type manipulation, variadic generics have obvious benefits. They let you go from manipulating types to collections of types. One example is in SQL libraries. If you have a trait `DatabaseColumnStorable` for types you can store in a database column (integers, strings, etc), then with variadics you could have a `struct DatabaseRow<...Columns: DatabaseColumnStorable>(...Columns)` to represent rows of a database table in a type-safe manner.
- Ygg2 3y agoAny time you see traits going impl Trait for (T1) impl Trait for (T1, T2) impl Trait for (T1, T2, T3) ... And bunch of macro invocations in the body on (T1), (T1, T2), etc. Someone, somewhere cursed there weren't variadics there.
- kolektiv 3y agoI'm writing a library at the moment with exactly this kind of issue (not quite that formulation, but morally equivalent). The whole thing would be a lot simpler (and remove the need for procedural macros to generate all of the types and impls) with variadic generics. Would I use variadic generics regularly? Probably not. But in places where they'd be useful - boy, would they really be useful!
- dathinab 3y agoalso some probably isn't good at using macros because you can make that a single macro which kinda is a bit like variadics. Through it still is limited to some specific number of your size (typical 16 or 32). Anyway not having varadic generics by itself is in most cases just a small inconvenience with limited benefits and there are many far more important priorities IMHO. The main benefit for varadic generics is that they might unblock some other improvements on top of them.
- SkiFire13 3y agoFor a quick example, Bevy needs to implement some trait for functions with a variable number of inputs. However it doesn't stop there, it also needs to store some state for each input, in other words it need to map that `fn(...T)` into a `(...State<T>)`. How do you model this with generics? When I tried to think about this every way I found to express this didn't allow the consumer code to call any methods on those `State`s, essentially making this useless.
- jcelerier 3y agoIn https://GitHub.com/celtera/avendish https://GitHub.com/celtera/avendish it's used to do operations on (heterogeneous) I/O ports of data flow objects (simply represented by variables of basic types.. float, int, string, etc.). This library would be entirely impossible in c++ without that feature.
- nicoburns 3y agoWith variadics the types would still implement an interface, they just allow you to work with those types t compile time rather than type-erasing them into a dynamic trait object.
- panzi 3y agoI wonder if one could write a truly statically typed tensor implementation with something like this. Where you can then say Tensor<f64, 16, 16, 2000> but also Tensor<bool, 32> and Tensor<f32, 4, 4> etc. Then some way of specializing for certain sizes to better use SIMD instructions etc. Even if it's just via something like `const if` inside of a single method, but with the assurance that the if is applied at compile time and thus e.g. illegal element access in the other branch is ignored etc.
- eru 3y agoWhy not something like Tensor<element_type, index_type> where index_type can then be your 16 x 16 x 2000 or 32 or 4 x 4 etc?
- panzi 3y agoHow would that work in Rust?
- nerdponx 3y agoFrom the perpsective pure type checking, Python/Numpy is heading in this direction: https://peps.python.org/pep-0646/#motivation https://peps.python.org/pep-0646/#motivation. However what you can't do without dependent types (as far as I know) is infer or verify statically that the transpose of a Tensor<f64,20,10> is a Tensor<f64,10,20>.
- gpderetta 3y agoSomething like this you mean: https://gcc.godbolt.org/z/P8475n4sc https://gcc.godbolt.org/z/P8475n4sc? Edit: I think that dependent typing is needed only of the matrix bounds are only known at runtime.
- cpgxiii 3y agoIn C++, Eigen comes pretty close to this interface for defining vectors and matrices and mixing size-at-compile-time and dynamically sized quantities. And then, given the size of the vectors/matrices being used, dispatching the appropriate SIMD implementation.
- j-pb 3y agoFocus on the compile error story. In particular, the design should avoid anything that leads to post-monomorphization errors. This is the single biggest mistake of Rust imho. I don't care in which compiler pass an error occurs as long as it happens at compile time. Rust has this anyways, you can already create types that fail after monomorphization, so you don't get any of the touted advantages anyways. You might as well bite the bullet accept that the people who say “You guys are dumb, just copy Zig’s type system” have a point, and shed some of the ivory tower dogma in exchange for a better language. It's completely fine for monomorphization to fail, and it allows you to create much more powerfull types. Rust is already incredibly complex, and most of that complexity comes from dozens of different systems that share few concepts all with their own syntax (modules, macros, traits, e.t.c.), so adding yet another syntax for static loops begs the question if some of this can't be unified. Zig is a seriously flawed language, but comptime is the right abstraction for metaprogramming in an imperative language.
- ngrilly 3y agoA bit off-topic, but since you mentioned it, why is Zig a "seriously flawed language"?
- j-pb 3y agoPriorities. The team is too focused on global maxima, speed and reinventing compiler design at the cost of a reference implementation that's fundamentally broken, and too complex to fix. This results in miscompilations, broken buildins, features added that are then ripped out again. I think fast iterations and experimental stuff are ok if you have a stable core. I.e. I don't mind if zig decides to change its for loop syntax after 5 years, so long as there is a simpler stable construct like `loop { break; }` that people that use the language in production and library maintainers can use. You need a certain critical mass for a language to be self sustaining, and that requires people to actually use your stuff, which in turn requires at least some stability guarantees for some subset of the language. Zig's response to that is "I'm just my inventors toy yet, but it will be grand when version 1.0 hits in 10-50 years." The fact that they now want to replace LLVM with their own backend makes me think that even 50 years would be optimistic.
- Permik 3y agoRelevant reading: https://soasis.org/posts/a-mirror-for-rust-a-plan-for-generic-compile-time-introspection-in-rust/#variadics-do-not-exist-in-rust https://soasis.org/posts/a-mirror-for-rust-a-plan-for-generi... All of this would've come for "free" (lets and lots of developer hours) if introspection is further developed. It's truly a tragedy that things went down how they did.
- Georgelemental 3y agoVariadics are a prerequisite for this sort of introspection, not an alternative. The design you linked uses an ad-hoc keyword to hack around their lack.
- Permik 3y agoI agree, but I think there's a possibility of keeping the variadicity only as introspection "compiler magic" and coming up with a higher level implementation of variadics for the general language that drops naturally out of introspection. My hope is that eventually variadicity will just be "only library code" that you can just inspect how it is implemented via introspection.