Hacker News new | past | comments | ask | show | jobs | submit login

I am extremely excited for this feature, especially now that I live in "absolutely no allocations" embedded land.

But also, beyond that, this is pretty much the last major feature that I've wanted in Rust. I've got some long-form writing in my head about this, but previously, I would have cast it as two or 2.5 features:

* const generics

* GATs

* specialization (this is the half)

However, when I've started to think about explaining these sorts of things, GATs (and to some degree specialization) feel much more to me like a removal of a restriction on combinations of features, more than "new features" strictly speaking. I think the line between these two perspectives is fascinating. On some level, you can even cast const generics in this way too: what it's really doing is making arrays a first-class feature of the language. I think that's a bigger stretch than GATs though, so I am not fully sure I'd make that argument. (The minimum feature we're stabilizing now basically does only this, but we do plan on going farther, so it feels true on a technicality now but not later.)

Regardless, it's been nice to see how much we've slowed down in adding major things. November of 2019 was the last time we had a feature this large hit stable. 18 months between huge things feels much more like the cadence of more mature languages that have been around a lot longer than Rust.

There is still a lot of work to do removing restrictions on existing features, especially const fn and const generics. But Rust is really starting to feel "done" to me, personally.




I think it probably falls more into the category of removing a restriction rather than a new feature, but I'm still waiting on HKTs - not for traits (as a former die-hard haskeller, I have been gradually converted to the side of "rust doesn't need a Monad trait") but for data types. There's a design pattern in haskell called "Higher-Kinded-Data"[0] where you parametrize a datatype on a type-constructor, and use that to generically define either partial or total versions of that data type- something like (in rust syntax):

    struct Person<F> {
        pub name: F<String>,
        pub age: F<u32>,
    }
where you can then have `Person<Option>` be a person that may or may not have all fields filled, and `Person<Box>` be a person with all fields definitely filled. This is something I find myself reaching for surprisingly frequently when writing rust, and I feel like it's a missed benefit of implementing higher-kinded types.

[0]: https://reasonablypolymorphic.com/blog/higher-kinded-data/

All that said, I'm really excited to have const generics land! Props to all the amazing work by withoutboats and the entire rust team.


In TS this would be flipped:

    type Person = {
     name: string;
     age: number;
    }

    type SomeFieldsMissing = Partial<Person>;
    type AllFieldsRequired = Required<Person>; // No real change in this case
Where Partial and Required are defined like:

    type Partial<T> = { [P in keyof T]?: T[P] | undefined; }

    type Required<T> = { [P in keyof T]-?: T[P]; }
What would you think of something like this ("mapped types")? They can get fairly powerful: https://www.typescriptlang.org/docs/handbook/2/mapped-types....


that seems like something that would only really work with a type system that's as structural as TS (vs rust, which is very nominal)


What would happen if I wanted only some fields to be required? To borrow from the GP example, something like this:

    struct Person<F> {
        pub name: F<String>,
        pub age: Option<u32>,
    }
With `Person<Box>` still leaving `age` as optional.


You can do it like this:

  type RequiredKeys<O, K extends keyof O> = Omit<O, K> & Required<Pick<O, K>>
  type Person = {
    name?: string
    age?: number
  }
  type RequiredName = RequiredKeys<Person, 'name'>
The opposite (making some of the fields optional, without affecting the rest) would be written like this:

  type OptionalKeys<O, K extends keyof O> = Omit<O, K> & Partial<Pick<O, K>>


Yes, you can do this in typescript, but you can also lie about it in typescript. To do it provably, you would construct a function that accepted the more generic version and then, using type assertions (ie the typescript compiler infers the type from declared runtime behavior), it would return a narrowed type.

If one caller passes our type narrower with {age:F<u32>} then the return value of the function will be narrowed to that type, but a broader criteria can still be specified for the generic instance.


Add an extra generic parameter.

(Also, it shouldn't be Box; you'd want a bog-standard generic newtype for this, not an extra heap allocation.)


yeah, I picked Box mostly as a pedagogical instance of the identity functor that already exists in the stdlib - in the real world you'd definitely want `struct Identity<F>(F)` instead.


Yeah there's going to be tradeoffs either way, the question is how often they come up in the situations the language is designed to target (versus random examples designed to showcase some obscure corner of PLT)... In this case for TS you might craft your own mappings that work like AllButXOptional<Person, 'age'>, or similar. So it's controlled externally versus internally, which who knows if thats better or not. One benefit of the TS approach is that it's a bit more composable... you can imagine things like OptionalIfRequiredInOther<ComplexPerson, SimplePerson>.


That superficially looks horrible. How do you write `Person (Compose Maybe Maybe)`, even, never mind anything more sophisticated?

Edit, for that matter, how do you deal with a `Person f` that includes a `f (Maybe Something)` without confusing "the user didn't specify a `Maybe Something`" with "the user specified a `Maybe Something` with value `Nothing`" (or vice versa)?


I'm not sure what you mean by `Person (Compose Maybe Maybe)`? Is that a person with values you have to unwrap twice to use? That's not really a thing in TS, it either is undefined or isn't. But if you need to shoehorn Haskell into TS for whatever reason I guess you could do something like:

    type Maybe<T> = {hasValue: false} | {hasValue: true, value: T}
    type Shoehorn<T> = { [P in keyof T]-?: Maybe<Maybe<P>> }


> I'm not sure what you mean by `Person (Compose Maybe Maybe)`? Is that a person with values you have to unwrap twice to use?

`Compose Maybe Maybe T` (equivalently `Maybe (Maybe T))`), has N + 2 values (where N in the probably-infinite number of values T has):

. N values consisting of a T

. 2 different values that do not include a T

It's also equivalent (via 1 + (1 + N) = (1 + 1) + N) to `Either Bool T`.

And yes, `Person f` contains values of type `f Whatever`.

> it either is undefined or isn't.

Values are never undefined. They might be defined as a object that's called "undefined", but that is a specific, defined, value. And, apropos of the above, it's a single value, out of the two distinct non-T values being represented. Which is part of why the example at https://news.ycombinator.com/item?id=26277598 looks horrible - it seems like it would confuse the two different non-T values with each other if `Person f` includes a `f (Maybe T)`.

Edit: actually, clarified the previous comment a bit, too.


> > it either is undefined or isn't.

> Values are never undefined.

I am referring to the JS concept of the singleton `undefined`, not an abstract idea of definedness.

Overall I see where you're coming from, but scrambling Haskell and TypeScript syntax is severely impacting legibility.

End of the day Haskell and TS have very different use cases, one is a research language exploring the reaches of type theory, the other is type system bolted onto a dynamic language doing the best it can. Sure there are some concepts that it can't express, but that's entirely missing the point.


I don't know what kind of software you write, but I build the same CRUD app and UI-flavor-of-the-month frontend, over and over.

I have no clue what "N + 2 values (where N in the probably-infinite number of values T has): N values consisting of a T" means and I've done fairly well for myself.

Why does anyone need this to write and deploy CRUD apps and put forms + buttons on stuff?


Parent's Haskell background is leaking, but the idea is "wouldn't it be cool if you could have a value that was either "A box containing a box containing a Person", "A box containing a box containing nothing", or "A box containing nothing""?? How supremely useful and not at all confusing would that be?

I kid, but I admit there is a valid division between coding to explore the consequences logical frameworks and coding to extract money from corporations while providing them a way to keep their orders coming in and accounted for. I'll gladly spend time on both sides of the spectrum, depending on how many drugs are in my system and how much food is in my belly :)


I like and enjoy HKTs in other languages, but don't feel a super strong need for them in Rust. I know some other people differ. It is very unclear when, if ever, Rust will get HKTs proper. GATs cover a lot of the same ground. We'll see :)


Wow, higher-kinded data. I've never heard of that, but it would absolutely be useful. E.g. for converting JSON inputs, where any field might be missing, into a version of the data which definitely has values for all the fields. Or a version of the struct where the values are actually read from a cache/database.


I proposed using it to model lifecycles of entities:

https://github.com/tim-group/higher-kinded-lifecycle/blob/ma...

(in this code, "idea" is a domain concept from the firm i worked for at the time - basically a recommendation to buy a stock, which is 'opened' on a certain date, and 'closed' when it no longer seems like a good recommendation)

My collegues didn't like it, and stuck to using separate types for objects in different stages of the lifecycle!


TypeScript (as you might expect from a lang built for dealing with JS Objects), has really great tooling for this kind of type narrowing these days (including on string template matching now) and I miss it so much now that I’m writing Go.


yes! once you use it you start wanting it everywhere, which is a big part of why I wish I could do it in Rust


This might be of interest to you (this is the second article in the series, and they've been good so far)

https://rustyyato.github.io/type/system,type/families/2021/0...


> where you can then have `Person<Option>` ... This is something I find myself reaching for surprisingly frequently

What's a practical use case for it? It's cool in theory, but I struggle to come up with something that doesn't use at most 2 implementations (just copy the struct then), or some API where the types don't mix and can be generated via a macro instead.


After writing embedded Rust, loving it, and then losing that job, I've gotta ask what you're up to these days (mostly so I can live vicariously through you). I am dying to write Rust in the embedded space again. :)


Oh no! Hope you can get back to it soon.

https://oxide.computer/

https://news.ycombinator.com/item?id=23975445 is probably the most accessible thing we've talked about publicly with what we're doing. (This talk was what made me decide to apply.) Still early enough the focus is on building the product rather than talking about it to broad audiences like HN.


Hire me! Jumps-up-and-down over-eagerly like a nerdy new college grad. I'll wave around cheerleading pompoms or aircraft marshaling wands until you do. ; D

Excessive, horn-tooting quasi- resume/cover letter in an HN comment (maybe I should've compressed and uuencoded?).

Home: Austin downtown, via much of NorCal { Chico, Sacramento, and Bay Area }, from San Jose.

Experience with embedded systems on many architectures and different types of RT deadlines, SMT electronics (PCB design and fabrication, stuffing, and repair), Rust (I sure hope so!), C/C++ (autodidacted at 15), LLVM, several assembly languages (ARM, X86, MIPS), microcontrollers, large-scale industrial firmware development (automotive, mining, & agriculture).

I periodically throw 3D printing and Arduino or ESP32 at personal use-cases. And trinocular microscope, Siglent scope, and knock-off compatible JBC solder station. The Fluke 289 DMM is real though.

Can do everything from sales engineering, on-prem/remote custom client integration, customer training, feature development, tools support, customer bug forensics, subgroup lead SWE, systems architecture/engineering, customer solutions development. Make the office more funner now and then (especially internal funny 404 pages and easter eggs, tape your chair up, orbeez explosion, get your wife/SO and kids in on a gaslighting long-con). 5¢ charge per instance. Wooden nickels not accepted. No refunds. :)

Did one or two minor humblebragging things:

- Ported a ridiculous nuclear reactor simulator in Fortran from UNIX to Windows.

- IBM Almaden offered me a dark matter research assistant job when I was 15, but my parents weren't supportive and it would've been illegal. :Y U No moon meme here:

- Wrote a Java-- to native MIPS (non-JIT, compile-time) compiler from scratch with symmetrical implementations in Java and C++.

- Refactored the heck out of a UDP 900 MHz packet radio firmware C++ codebase and added telemetry logging with error tolerance of Flash EEPROM wear beyond mfgr specs.

- Restoring & modding a VW camper and getting into paramotoring (parachute wing and motor on your back).


Oh hi neighbor! I also live in downtown Austin.

Please apply! We are hiring.


Heck ya! : D I could work on embedded and datacenter gear for free if I didn't have bills and a spinning circular vinyl noise-makers addiction.

Yeah, I've been pining for highly-integrated, hyperscale datacenter metal. Only Fortune 50 corporations and partakers of OEMs/ODMs like Quanta QCT / Dell DCS deliver somewhat okay customized gear, but not a completely-integrated, managed fleet. I remember eGenera BladeFrame, but it's still nothing like a hyperscale, intermodal-container LEGO datacenter solution.

Oh hey from I-35 and 5th!

Here's your "I probably survived the 2021 ATX Icepocalypse" tie-dyed hoodie with black light effects.


> Please apply! We are hiring.

You certainly aren't replying to you applicants, though.


Hm, we should be! Sorry about that. The aim is to reply to literally everyone. If you wanna email me, I'm happy to look into it.

EDIT: I decided to take a look, and it seems like you applied before I joined. My apologies, it must have been a mistake.


> My apologies, it must have been a mistake.

Well that mistake seems to keep repeating itself then.

Oxide has been silent even after subsequent contact, and even after this issue has already been raised internally once.


Silence is usually a soft "no." Good news [usually] travels fast. I usually apply again another time and try to be chill about rejection. :)


Steve works at Oxide Computer and they are hiring. :)

https://oxide.computer/careers/software-engineering/


TY, probable Moz Rustacean. :hands together emoji:

I've been living on savings in self-sabbatical mode while catching-up on life, projects, and reading while keeping current with Rust.

I'm soo itching to jump back into workaholism mode.


Check out FullStory - I recently left the company but we were looking for an iOS role that would involve a ton of Rust, and embedded systems people were definitely a good fit for that position.


For me, a feature I'd really hope for is the elusive "if not let" (or however this gets named eventually), to let me escape the currently unavoidable clutches of Rust's rightwards drift and accruing mental context.


Swift has had ‘guard’ statements since 2015, and it’s the biggest thing I miss when programming in Rust:

    guard let x = ... else {
        // must not fall through, so a return, break, throw, non-returning function call, etc. is required
    }
    // do something with x


In Rust I would just do:

    let x = match foo {
        Some(x) => ...,
        None => return // or break, etc.
    };
    // do something with x
(This is assuming that you don't want to propagate an error or panic, which could be more easily done with `foo?` or `foo.unwrap()` respectively.)


A nice quality of the `guard` in swift is that you can chain multiple conditions, which is harder to do in rust (maybe with some macros a la if_chain?)


I ran into this the other day, and it took me a moment to realise that I could just use `matches!`:

    // want to do this
    if not let Some("pattern") = val {
        doSomething();
    }

    // can instead do
    if !matches!(val, Some("pattern")) {
        doSomething();
    }


That won’t allow extracting a value from the optional though, will it? i.e. I can’t do this something along the lines of:

    if !matches!(val, Some(let x))


Indeed not, so it's not a full replacement. I'm not entirely sure that I like the idea of an "if let" variant creating bindings that escape the visual block, but I'd probably get over it.


Lol it would be kinda funny to have this:

    let input: Option<usize> = Some(1);
    if let Some(value) != input {
        return 0;
    }
    // use value; such that value: usize = 1


We already have something close:

  let input: Option<usize> = Some(1);
  let value = match input {
    Some(x) => x,
    _ => return 0,
  };
  // use value; such that value: usize = 1


That works, but it's clunky enough that it defeats the purpose of writing guard statements IMO. It's not hard to understand, but it's also not possible to figure out what it means at-a-glance. I have to stare at it for a few seconds to try to figure out where all control flow is going to go -- it has two levels of nested subexpressions, along with branches that appear similar but do very different things (returning a value from the expression vs. from the function).

The point of a 'guard' clause is to visually distinguish between the "expected" and "unexpected" case. When I see a guard statement:

    let input: Option<usize> = Some(1);
    guard let Some(value) = input else {
        return 0;
    }
    // use value; such that value: usize = 1
I immediately know that the rest of the function expects input to contain a value, and that whatever is after the 'else' is some kind of error handler. Your match statement might have the same effect functionally, but visually it's confusing because it's not immediately obvious which branch is the happy-path. I'd rather read a function containing ten guard clauses than a function containing ten of those matches.


Some(1).unwrap_or(0)


Return 0 is different from using 0 in the rest of the function body.


Rust has this, it's `let ... = match { ... };`.


I’m an embedded rust developer myself, and have written my own stack with const_generics and am extremely excited to see const generics land in stable.

However (and I’d love your thoughts about this) the one feature I feel has not gotten the love it deserves is DST types and safely creating them. I was writing a zero-alloc no_std h264 and mpegts parser and the hoops I had to jump through to represent extant mpeg structures as valid rust data types was insane and I ran into a lot of edge cases with transmute that made me scratch my head. In the end, I wrote my own macro for creating (in-place new-ing) DST types that transmuted fat pointers (whose representation is not guaranteed but unlikely to change anytime soon) to get things kind of working but the ergonomics and safety were non-existent.

I feel like if rust doesn’t pick up the baton for innovating on DSTs it would be a huge shame since from a security and memory safety perspective they cause the lion’s share of overflow and out of bounds accesses in parsers and other high-risk libraries that silently digest all data thrown their way for media previews, thumbnail generation, etc etc.

What irked me is that a lot of people consider the checkbox for DST support to be ticked when there’s really only the bare minimum for interesting with types containing single DST elements that are already - somehow - created, typically by the standard library itself.

(Again from an embedded perspective: I’m really happy with async/await for embedded development; it really makes reasoning about control flow in an embedded context much more sane!)


I also wish for extensible enums and enum subset:

    pub enum Expr {
        Int,
        Str
    }
    
    pub enum Expr2 : Expr{
        Bool,
    }
    
    fn check(??)-> Expr.Int


And struct extend!:

  struct Person {
    id:i32
  }

  struct Customer: Person {
    
  }
P.D: This is not subclassing, is not redefine all the same attributes again and again. Is partially supported to copy the values but not defining them.


Could you please post an example of how it helps in embedded / no allocator code? I do a good deal of embedded on Rust, but am having a tough time figuring out when or why to use this from the abstract examples in the article.

Tangent: What's your take on the Rust HALs? (eg `stm32l4xx-hal`) etc? I'm curious what the take is of someone outside the Github and Matrix communities. They strike me as... remarkably underdeveloped, but have big potential.


> Could you please post an example of how it helps in embedded / no allocator code?

In today's Rust, data structures often need to be a fixed size, or dynamically allocate. This will let you write a data structure that has a variable size, but set at compile time. I knocked out a quick ringbuffer recently; I picked a size that seemed fine, but that size is fixed at the moment. If I had used const generics, it could have been more flexible here, I could have written it more easily for any size, where the size is written at compile time. This is not my work code, but back when I was working on a hobby x86 kernel, I did this for a VGA buffer: https://github.com/intermezzOS/kernel/blob/master/vga/src/li... it's generic over "something that can be converted to a slice," so that it uses a slice in production, but a Vec in tests. This also leads to runtime checks https://github.com/intermezzOS/kernel/blob/master/vga/src/li... to make sure that the slice is the right size. This would be much better written with const generics today. The slice trick is neat, but awkward. (This code is also awkward because it grew an internal "frame buffer," so it kinda has both going on. This example needs const generics less since VGA has one single size at compile time ever, but sometimes, you need something where this isn't the case, and this was the first example of code I personally wrote that springs to mind.)

> What's your take on the Rust HALs?

I haven't used them enough. We use the layer below them, for example, the stm32f4 crate rather than the stm32f4xx-hal crate. That doesn't mean they're bad, I just literally have not used them enough to form an opinion. The namesake of this space is diversity, and so different people make different tradeoffs; we don't really need what those crates are offering right now.


Thank you for the detailed info. I'll be on the lookout for cases to take advantage of this.


So excited about const generics. Is there any work on variadic templates. That is one of the big things I miss from c++. Macros can fulfill some of the needs, but not always.


Some people have made some proposals, but it's at an extremely early, if ever, state.


Upcoming website from Klabnik: www.arewedoneyet.rs




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: