News for hackers. Stuff that matters.
Re:Dave Herman’s contributions to Rust by stared on Monday May 03, 2021 @10:09AM (#27025283)
Personally, I find Rust syntax to be well-designed. At least, compared to any practical programming language I know.

Quite a few times I was surprised that Rust breaks with some old patterns that were copied over and over in the last 50 years or so. For example: "match" instead of "switch", or the same if/else regardless if it is a statement or a value. These are small touches, but they show attention to detail.

Re:Dave Herman’s contributions to Rust by yoneda on Monday May 03, 2021 @12:35PM (#27027172)
> Quite a few times I was surprised that Rust breaks with some old patterns that were copied over and over in the last 50 years or so. For example: "match" instead of "switch", or the same if/else regardless if it is a statement or a value. These are small touches, but they show attention to detail.

These are good ideas for sure, but they are bread and butter to anyone who is familiar with functional programming. The Lisp and ML families of languages have had them for many decades.

The fact that more languages _aren't_ like this is what's surprising to me, given how effective they are. I love that Rust is bringing them to the masses, but what on earth took the industry so long to accept them?

I guess the answer is that algebraic data types (inductively defined data) tend to be pitted against object-oriented programming (coinductively defined data), and object-oriented programming has dominated the industry for the past 3 decades. Some languages like Kotlin have tried to combine them, but personally I'd rather just embrace the former and relegate the latter to a seldomly-used design pattern, not a programming paradigm hardcoded into the language.

Re:Dave Herman’s contributions to Rust by dahfizz on Monday May 03, 2021 @12:50PM (#27027407)
Rust reminds me a lot of Scala in that respect. Not really a functional language, but with functional goodies sprinkled throughout.
Re:Dave Herman’s contributions to Rust by linkdd on Monday May 03, 2021 @11:27AM (#27026316)
Rust syntax is weird. Weirdly good and sometimes bad.

I'm currently designing my own toy language and writing the compiler (to LLVM IR) in Rust.

Representing the AST with Rust's sum types is so simple. Visiting that AST through pattern matching is great. But the "enum" keyword still bugs me.

The way you define product types (tuples, records, empty types) and then their implementation, just awesome. But the "struct" keyword still bugs me too.

It feels "high level" with some quirks.

Then you have references, Box, Rc, Arc, Cell, lifetimes etc... It feels (rightfully) "low level".

Then you have traits, the relative difficulty (mostly for Rust newbies like me) of composing Result types with distinct error types, etc...

It feels "somewhat high level but still low level".

Sometimes you can think only about your algorithm, some other times you have to know how the compiler works. It seems logical for such a language, but still bugs me.

The one thing I hate though, is the defensive programming pattern. I just validated that JSON structure with a JSON schema, so I KNOW that the data is valid. Why do I need to `.unwrap().as_string().unwrap()` everywhere I use this immutable data?

Re:Dave Herman’s contributions to Rust by coolreader18 on Monday May 03, 2021 @11:40AM (#27026480)
Because ideally your JSON schema validator would turn it into a type that mirrors the structure of the data. "Parse, don't validate"[0]

[0]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...

Re:Dave Herman’s contributions to Rust by linkdd on Monday May 03, 2021 @11:45AM (#27026536)
But the Rust type system cannot fully express a JSON Schema:

  {
    "type": "object",
    "oneOf": [{
      "required": ["kind", "foobar"],
      "properties": {
        "kind": {"enum": ["foo"]},
        "foobar": {"type": "string"}
      }
    }, {
      "required": ["kind", "barbaz"],
      "properties": {
        "kind": {"enum": ["bar"]},
        "foobar": {"type": "number"},
        "barbaz": {"type": "string"}
      }
    }]
  }
Or am I wrong?
Re:Dave Herman’s contributions to Rust by masklinn on Monday May 03, 2021 @12:40PM (#27027255)
I'm sure you can design schemas screwy enough that Rust can not even express them[0] but that one seems straightforward enough:

    #[derive(Serialize, Deserialize)]
    #[serde(tag = "kind", rename_all = "lowercase")]
    enum X {
        Foo { foobar: String },
        Bar {
            #[serde(skip_serializing_if = "Option::is_none")]
            foobar: Option<f64>, 
            barbaz: String
        }
    }
[0] an enum of numbers would be an issue for instance, though I guess you could always use a `repr(C)` enum it might look a bit odd and naming would be difficult.
Re:Dave Herman’s contributions to Rust by remexre on Monday May 03, 2021 @11:58AM (#27026712)
In general, JSON Schemas are (wrongly, in my view...) validation-oriented rather than type-oriented (for notions of types that would be familiar to Haskell, Rust, or Common Lisp programmers).

I think that schema in particular could be represented, though, as:

    enum Thing {
        foo { foobar: String },
        bar { foobar: Option<f32>, barbaz: String },
    }
Re:Dave Herman’s contributions to Rust by linkdd on Monday May 03, 2021 @12:16PM (#27026942)
What about user-supplied JSON schemas? You can't add types at runtime.

Also, JSON schemas allows you to encode semantics about the value not only their types:

  {"type": "string", "format": "url"}
That's something I like about Typescript's type system btw:

  type Role = 'admin' | 'moderator' | 'member' | 'anonymous'
It's still a string, in Rust you would need an enum and a deserializer from the string to the enum.
Re:Dave Herman’s contributions to Rust by masklinn on Monday May 03, 2021 @12:49PM (#27027387)
> What about user-supplied JSON schemas? You can't add types at runtime.

That kinda sounds like you just launched the goalposts into the ocean right here.

> Also, JSON schemas allows you to encode semantics about the value not only their types:

JSON schemas encode types as constraints, because "type" is just the "trival" JSON type. "URL" has no reason not to be a type.

> in Rust you would need an enum

Yes? Enumerations get encoded as enums, that sounds logical.

> a deserializer from the string to the enum.

Here's how complex the deserializer is:

    #[derive(Deserialize)]
    #[serde(rename_all = "lowercase")]
    enum Role { Admin, Moderator, Member, Anonymous }
And the second line is only there because we want the internal Rust code to look like Rust.
Re:Dave Herman’s contributions to Rust by linkdd on Monday May 03, 2021 @01:01PM (#27027604)
Yep, I'm still new to serde and the Deserialize workflow :)

I come from highly dynamic languages, and even when I was doing C/C++ 10 years ago, I would do more at runtime that what could be considered "best practice".

Re:Dave Herman’s contributions to Rust by steveklabnik on Monday May 03, 2021 @12:27PM (#27027068)
As the input gets more dynamic, so does the type system representation. If you want to handle user-supplied JSON schemas, in my understanding of JSON Schema, you'd have to use the serde_json::Value way: https://docs.serde.rs/serde_json/#operating-on-untyped-json-...
Re:Dave Herman’s contributions to Rust by remexre on Monday May 03, 2021 @12:41PM (#27027268)
> What about user-supplied JSON schemas? You can't add types at runtime.

Right, well, since they're validators anyway, might as well represent them as a defunctionalized validation function or something. Agreed that this is more-or-less past the point where the type system helps model the values you're validating, though a strong type system helps a lot implementing the validators!

> It's still a string, in Rust you would need an enum and a deserializer from the string to the enum.

Yep, though if you really wanted it to be a string at runtime, you could use smart constructors to make it so. The downsides would be, unless you normalized the string (at which point, just use an enum TBH), you're doing O(n) comparison, and you're keeping memory alive, whether by owning it, leaking it, reference counting, [...].

Thankfully due to Rust's #[derive] feature, the programmer wouldn't need to write the serializer/deserializer though; crates like strum can generate it for you, such that you can simply write:

    use strum::{AsRefStr, EnumString};
    
    #[derive(AsRefStr, EnumString, PartialEq)]
    enum Role {
        Admin,
        Moderator,
        Member,
        Anonymous,
    }
    
    fn main() {
        assert_eq!(Role::from_str("Admin").unwrap(), Role::Admin);
        assert_eq!(Role::Member.as_ref(), "Member");
    }
(strum also has also a derive for the standard library Display trait, which provides a .to_string() method, but this has the disadvantage of heap allocating; EnumString (which provides .as_ref()) compiles in the strings, so no allocation is needed, and .as_ref() is a simple table lookup.)

[0]: https://docs.rs/strum/0.20.0/strum/index.html

Re:Dave Herman’s contributions to Rust by masklinn on Monday May 03, 2021 @01:32PM (#27028049)
> Yep, though if you really wanted it to be a string at runtime, you could use smart constructors to make it so. The downsides would be, unless you normalized the string (at which point, just use an enum TBH), you're doing O(n) comparison, and you're keeping memory alive, whether by owning it, leaking it, reference counting, [...].

Nit: it's more constraining but serde can deserialize to an &str, though that assumes the value has no escapes.

Ideally `Cow<str>` would be the solution, but while it kind-of is, that doesn't actually work out of the box: https://github.com/serde-rs/serde/issues/1852

Re:Dave Herman’s contributions to Rust by linkdd on Monday May 03, 2021 @01:04PM (#27027652)
Thanks for the insight! Didn't know strum.

But yeah, I tend to do more work at runtime than compile-time, which is not really the way to go in Rust.

Re:Dave Herman’s contributions to Rust by willtim on Monday May 03, 2021 @12:06PM (#27026809)
Unfortunately Rust is currently lacking structural records/structs and enums. I think they removed them early on in the design. So you'd have to name the all the types. I hope they do add them back one day.
Re:Dave Herman’s contributions to Rust by da39a3ee on Monday May 03, 2021 @03:19PM (#27029492)
I think that there's still time for Rust to reverse the .expect(...) naming mistake. It should be deprecated, either introducing something with an appropriate name like .unwrap_or_panic(...) or just leave it to be replaced by .unwrap_or_else(|| panic!(...)) which IMO one ought to use today instead of .expect(...).

Everything else is named so well and designed so well that this really sticks out.

Re:Dave Herman’s contributions to Rust by iudqnolq on Tuesday May 04, 2021 @11:42AM (#27038816)
Why don't you like expect? It feels pretty natural to me.

    foo.expect("already validated")
Re:Dave Herman’s contributions to Rust by da39a3ee on Wednesday May 05, 2021 @03:07PM (#27054258)
That reads as "I expect this to be already validated". Is that what you intended it to mean? Similarly,

  foo.expect("There was a problem and I had to crash")
That _reads_ as "I expect there to be a problem and have to crash". But it _means_ something totally different. It means:

  There should not be a problem, but if there were a problem
  I would crash and print out this message.
And the way to write that in readable code is

  foo.unwrap_or_else(|| panic!("There was a problem and I had to crash"))
I'm sorry if I'm misunderstanding -- if I am can you explain to me how? Because I feel like my view is diametrically opposed to yours but it's really not that subjective a matter; only one of us can be right.
Re:Dave Herman’s contributions to Rust by iudqnolq on Wednesday May 05, 2021 @03:10PM (#27054305)
You just phrase things the opposite way. I don't think it's clearly better to say the expected happy path or the unexpected sad path in a crash message.

> That reads as "I expect this to be already validated". Is that what you intended it to mean?

Yes

> foo.expect("There was a problem and I had to crash")

> That _reads_ as "I expect there to be a problem and have to crash". But it _means_ something totally different. It means:

Absolutely. That's why I'd phrase it foo.expect("foo invariant upheld")

Re:Dave Herman’s contributions to Rust by da39a3ee on Wednesday May 05, 2021 @08:35PM (#27057743)
OK, that would be nice but...what you're saying just doesn't work! The user gets an error message saying

  thread 'main' panicked at 'foo invariant upheld'
And everyone, except those who are inured to the illogic via experience, will read that as

  thread 'main' panicked because 'foo invariant upheld'
especially new users, less experienced programmers, and non-Rust programmers seeing the panic message.

Now if the Rust language made that error message say

  thread 'main' panicked at failed expectation for 'foo invariant upheld'
that might be cool. But it does not do that. It feels like you're bending over backwards trying to excuse what is clearly a wart in a beautiful language. I'm happy to continue the debate though, I am fascinated to know how I can be wrong here :)

So that's my main point, that your strategy doesn't work due to the error message. But a secondary point is that Rust documentation encourages people to use it opposite to what you suggest: I mean, just look at the first example in the Rust book. It's completely nonsensical:

  let f = File::open("hello.txt").expect("Failed to open hello.txt");

I know there was some controversy about this early on and somehow it didn't get reverted; it would be more honest if the docs would at least admit that it's "a little strange".

https://doc.rust-lang.org/book/ch09-02-recoverable-errors-wi...

Playground if you want to remind yourself of the psychological experience of seeing that error message at run time with minimal effort.

https://play.rust-lang.org/?version=stable&mode=debug&editio...

Re:Dave Herman’s contributions to Rust by iudqnolq on Thursday May 06, 2021 @05:50PM (#27069462)
Outside of tests and toy examples you're generally expected to only panic when the developer has failed to uphold an invariant, not when the user has. As a result there's generally nothing a non-rust-developer can do with the message besides post it on the bug tracker.
Re:Dave Herman’s contributions to Rust by da39a3ee on Thursday May 06, 2021 @09:34PM (#27070949)
Right, so that's relevant to a minor incidental comment I made. But what do you think of the main points, which are

(a) If you write the message the way you suggest the printed error message is misleading, and

(b) The Rust community does not encourage using .expect() in the way you suggest; in particular the canonical Rust documentation source instructs us to use it as

  foo.expect("A thing I do not expect")
Re:Dave Herman’s contributions to Rust by da39a3ee on Sunday May 09, 2021 @08:57AM (#27095584)
Similarly, from the rust stdlib docs for process::Command

  let hello_1 = echo_hello.output().expect("failed to execute process");
Re:Dave Herman’s contributions to Rust by sireat on Monday May 03, 2021 @10:35AM (#27025592)
Syntax wise Rust seems heavily inspired by the "good parts" of Scala.

- Pattern matching via "match"

- if being an expression (among most things)

Those are both found in Scala.

Re:Dave Herman’s contributions to Rust by lgessler on Monday May 03, 2021 @10:53AM (#27025802)
Didn't Scala get both of those things from ML?
Re:Dave Herman’s contributions to Rust by Jtsummers on Monday May 03, 2021 @11:32AM (#27026375)
Yes, and Rust has more direct lineage to OCaml than Scala (initial version was written in OCaml, and the language had a more ML-ish syntax early on).
Re:Dave Herman’s contributions to Rust by sireat on Monday May 03, 2021 @11:57AM (#27026709)
I guess then it would be more correct to say that Scala and Rust share a common inspiration in ML languages.