HNHacker News
TopNewBestAskShowJobs

shepmaster

1,167 karma · joined February 6, 2011

Cofounder of the world's first Rust consultancy[1] — we are available to help you and your company with any and everything related to Rust! We also produce the Rust in Motion video series[2] for Manning.

[1]: http://integer32.com/ [2]: https://www.manning.com/livevideo/rust-in-motion?a_aid=jgoulding&a_bid=6a993c2e

submissionscomments
shepmaster··on Maud: A Rust macro for writing HTML
> println! is a macro, and doesn't "break the tooling"

I like, use, and write macros, but this isn't fully true. For example, recent Rust versions allow writing identifiers directly in the format string:

    println!("{some_identifier}")
However, this breaks rust-analyzer's ability to rename variables [1].

[1]: https://github.com/rust-lang/rust-analyzer/issues/11260#

shepmaster··on Nim: Curated Packages
> and crates like num_cpus

https://doc.rust-lang.org/std/thread/fn.available_parallelis... — Rust 1.59

shepmaster··on Our Experience Porting the YJIT Ruby Compiler to Rust
And chiming in:

> there's no reason why you couldn’t safely promote a u8, u16, or u32 into a usize

The people running Rust on a 16-bit architecture (e.g. AVR) would disagree. On those platforms, usize is 16-bit, so promoting u32 to usize would not be possible.

I _do_ wish there was a nicer way to declare up-front "I *never* want this crate to compile on something smaller than 32-bit (or 64-bit), so assume that".

IIRC, similar ideas like this have floated around under the name of a "platform compatibility lint".

shepmaster··on Ask HN: Do you rewrite pull requests?
I strive to have every commit be meaningful/buildable/testable/etc. To that end, I use a lot of `git rebase -x` to massage the pull request into a reasonable sequence of commits that “tell a story”. Having a bunch of “fixing foo” commits is ugly to me and reduces my ability to bisect problems later on.

Sometimes I will actually create the fixup commits and push them separately to communicate with the submitter, but then squash them into the appropriate places in history before merging.

The only technical issue is when someone has signed their commits, as I obviously cannot re-sign them :-). In that case, I call it out to them and let them re-sign if they want before I merge.

shepmaster··on Ask HN: Do you rewrite pull requests?
I will type up all the review comments first, then...

If I expect or hope the contributor to become a long-term collaborator, I will submit the comments and wait for them to fix it. I'd rather they learn

If I don't expect them to stay around, I'll make the changes, force-push to their branch keeping their authorship, submit the comments, and let them know what I did and link to the diff caused by just the force push. I'll then ask them if it's OK and let the PR sit for a few days or until they respond, then I'll merge it.

---

The more of your expectations you can automate or at least document (e.g. in CONTRIBUTING.md) the easier it is.

shepmaster··on Tell HN: Have just discovered that I can use any key as Ctrl on my keyboard\
> map Ctrl(hold) / Esc (momentary press)

Do you have an example of this configuration somewhere you could share? Or are you using something existing, like https://github.com/jasonrudolph/ControlEscape.spoon ?

shepmaster··on Rust9x: Compile Rust code for Windows 95, NT and above
https://github.com/rust-console/gba
shepmaster··on Is Rust Web Yet?
Ditto with my journey from Warp to Axum. The Rust Playground now uses[1] Axum as its backend and I’ve been pleased with working with the maintainers.

[1]: https://github.com/integer32llc/rust-playground/pull/777

shepmaster··on What Is Rust's Unsafe? (2019)
I’ll be even lazier (heh) and just straight up leak memory for true once-set read-only config values.

https://github.com/shepmaster/stack-overflow-relay/blob/273a...

shepmaster··on Announcing Actix Web v4.0
+1 for Axum. I recently moved [1] the Rust Playground's backend to Axum and have been very happy with it and the team producing it.

[1]: https://github.com/integer32llc/rust-playground/pull/777

shepmaster··on Rust data structures with circular references
I lean towards an arena.
shepmaster··on Builder Pattern in Rust
> assumes that String::new will be const at some point

It has been const since Rust 1.39 (https://doc.rust-lang.org/std/string/struct.String.html#meth...)

shepmaster··on Rust: Enums to Wrap Multiple Errors
I should have worded myself better, apologies.

With that example, since you use `Result::map_err` and specify the specific variant, you aren't really using anything from thiserror at the site of the `?`, correct?

Most usages I have seen, people use `#[from]`, which would end up having conflicting implementations. Is there a reason you didn't use `#[from]` for `ParseConfig`?

shepmaster··on Rust: Enums to Wrap Multiple Errors
> snafu vs thiserror/anyhow

I'd like to provide a fair comparison [1] in the documentation, but I don't know thiserror / anyhow well enough to feel like I'd give them the credit they are due.

That said, to my knowledge, thiserror doesn't allow you to take an `io::Error` and sort it into two different enum variants (like the `Reading` and `Opening` variants in my grandparent example). To me, those are vastly different error states that just both happen to have the same error type. You can extend the metaphor with any larger error type from a crate (e.g. `reqwest::Error`).

Anyhow requires using a trait object (and potentially downcasting) and I prefer avoiding those when possible.

> seemed the popularly recommended way

Absolutely. The author of those crates is a giant in the Rust community [2] (they are also the author of serde, syn, and quote, for example!). If those crates suit your situations, then by all means — use them. I'd much rather the Rust community have better error types and messages by whatever means available. Even using `String` via `Box<dyn Error>` is better than nothing.

> you won't get autocompletion

You should, at least if you use rust-analyzer. I use it via emacs and have these settings enabled, but I think they were going to be the default:

    (lsp-rust-analyzer-cargo-load-out-dirs-from-check t)
    (lsp-rust-analyzer-proc-macro-enable t)

[1]: https://docs.rs/snafu/0.7.0-beta.1/snafu/guide/comparison/in... [2]: https://crates.io/users/dtolnay
shepmaster··on Rust: Enums to Wrap Multiple Errors
> but is a whole crate worth it

Yes. I'm not sure exactly what other response you'd expect from the author/maintainer of a library when they've already made a post encouraging other people to use it. ¯\_(ツ)_/¯

> when you could just use e.g., `.map_err(|source| Error::Opening { source, filename })`

That's not equivalent, as `filename` is a `&str` but becomes a `String` when stored in the error. SNAFU automatically calls `Into::into` for you, so the closest would be:

    .map_err(|source| Error::Opening { source, filename: filename.into() })
> Seems like all `.context` provides

There's also the possibility of automatic construction of values (backtraces, location information, the current time, things captured from globals / thread locals, etc.)

Beyond `.context`, SNAFU also implements the `Error` trait (and associated methods like `Error::source`).

There's also convenience methods and macros to create leaf errors, those that originate in your code.

> obviously the `#[snafu(display(...))]` macros could just be moved into a `impl Debug for Error`

I'll assume you mean `Display`, not `Debug`. That also not quite true, as SNAFU offers a shorthand syntax that isn't yet in stable Rust:

    "Unable to read {filename}"
would need to be one of

   "Unable to read {}", filename
   "Unable to read {filename}", filename = filename

> like all [...] provides [...] obviously [..] could just be moved

All code could have been written by your own hand or otherwise inlined. Your response feels (needlessly) highly dismissive of another person's work.

shepmaster··on Rust: Enums to Wrap Multiple Errors
Which language(s) did exceptions the best, in your opinion?
shepmaster··on Rust: Enums to Wrap Multiple Errors
> how these errors are presented to the user, or what ends up in the logs in production. In those cases something like [...] are significantly more actionable than just the underlying I/O errors by themselves. To the point **where a person reading these messages has a shot at addressing the problem** without any prior knowledge of the code base.

(emphasis mine)

This is a huge belief of mine as well. I call it a "semantic stacktrace".

I actually go as far as to say that _most of the time_ backtraces in errors are an antipattern, as it gives a false sense that an error is actionable ("oh, just look in the code!").

I even try to make it such that each leaf error is constructed in exactly one place so that grepping for the error identifies that one location.

shepmaster··on Rust: Enums to Wrap Multiple Errors
> 15-20 enums (or more) for their error types and it makes things very cumbersome

Like everything, it can be a balancing act. I tend to be free about creating error types (one per module, usually, but it's not strange for me to create more). When you implement `std::error::Error` (to be able to make error trait objects) and have a tool like SNAFU (to make error enums), then having more error types isn't much of a hindrance in my experience.

> I’ll start with `anyhow` if I’m writing library code and then refactor to enums later

The newest (beta) version of SNAFU actually strives to make this case even smoother. There's a `whatever!` macro that you can use to create stringly-typed errors and then migrate case-by-case to an enum. Check out the docs for some example usage.

shepmaster··on Rust: Enums to Wrap Multiple Errors
> you should have the underlying message of the std::io::Error

This is a point of debate[1] among the error-handling working group.

[1]: https://github.com/rust-lang/project-error-handling/issues/4...

shepmaster··on Rust: Enums to Wrap Multiple Errors
> Propagation without needing to use boxed trait objects

You can also use enums, as shown in the post. You don't need trait objects.

EDIT 1

> without needing to use boxed trait objects [...] `anyhow`

It's my understanding that anyhow uses trait objects. The first sentence of its document says "This library provides anyhow::Error, a trait object based error type"

EDIT 1 END

> which have nice downcasting API

I'm not a fan of downcasting when not _absolutely_ necessary.

See my sibling comment about SNAFU for an alternate that allows keeping different errors separate while unifying them.

shepmaster··on Rust: Enums to Wrap Multiple Errors
Check out my sibling comment about SNAFU, which addresses your concerns, if I understand them.
shepmaster··on Rust: Enums to Wrap Multiple Errors
I'll hype my own library, SNAFU [1].

It simplifies constructing your own "leaf" errors and streamlines the ability of collecting multiple types of errors while attaching more context to them (e.g. filenames, stack traces, user ids, etc.). It allows you to smoothly switch from "stringly-typed" errors to strongly-typed errors. You can create opaque errors to avoid leaking internal implementation details into your public API.

Applied to the code in the post:

    use snafu::prelude::*;
    use std::{fs::File, io::prelude::*};
    
    #[derive(Debug, Snafu)]
    enum Error {
        #[snafu(display("Unable to open {filename}"))]
        Opening {
            source: std::io::Error,
            filename: String,
        },
    
        #[snafu(display("Unable to read {filename}"))]
        Reading {
            source: std::io::Error,
            filename: String,
        },
    
        #[snafu(display("Unable to parse {buffer} as a number"))]
        Parsing {
            source: std::num::ParseIntError,
            buffer: String,
        },
    }
    
    fn read_number_from_file(filename: &str) -> Result<u64, Error> {
        let mut file = File::open(filename).context(OpeningSnafu { filename })?;
    
        let mut buffer = String::new();
    
        file.read_to_string(&mut buffer)
            .context(ReadingSnafu { filename })?;
    
        let buffer = buffer.trim();
        let parsed: u64 = buffer.parse().context(ParsingSnafu { buffer })?;
    
        Ok(parsed)
    }
The key parts are the `derive(Snafu)` on the definition of the error enum and the usages of `.context` and `XxxSnafu` at the error sites.

Importantly, this example demonstrates a key feature of SNAFU, here shown as "not all `io::Error`s are the same". Opening the file and reading the file are two separate error conditions and should not be lumped together as one.

[1]: https://docs.rs/snafu/0.7.0-beta.1/snafu/index.html

shepmaster··on Composable Data Validation with Haskell
A concrete example would be the input (in JSON to pick a format)

    { "alpha": "3", "beta": "5" }
Here, the fields `alpha` and `beta` are serialized as strings but should be treated as numbers. If the input were malformed:

    { "alpha": "dog", "beta": "cat" }
Then I'd like to get two validation errors for a single validation attempt. For example:

    { "alpha": ["must be a number"], "beta": ["must be a number"] }
> I believe the system presented is the concurrent solution.

It sounds like you are saying that I'd be able to get something isomorphic to the above, so that's good.

In addition to the above, I'd like to be able to say that once the values have been validated (a.k.a. parsed) as numbers, then `beta` must always be greater than `alpha`. This is a type of validation across fields that also depends on earlier validations (parsing, transformation, etc.) succeeding.

shepmaster··on Composable Data Validation with Haskell
> the system would have to be aware of dependencies between pieces of data in order to do it correctly

Yep, which is why I expect that a graph is needed to model the validations that I picture in my mind.

> so that they can opt in to discarding error messages

Do you have any small examples of when you'd want to discard error messages?

shepmaster··on Composable Data Validation with Haskell
It’s not obvious to me: does this extend to supporting multiple concurrent validations? For example, can you perform a single validation of a structure and get back multiple validation results for each of multiple field?

From some thought experiments I’ve done, I feel like my ideal solution would be to define a graph based on my data structure with validations as the edges. Then traverse the graph as far as possible before reporting errors.

This would allow validations to build on each other, as well as validating arbitrary combinations of fields.

I’ve never sat down to actually implement it though…

shepmaster··on Rust Language Cheat Sheet
> If you are writing your own library then your best bet is to define your own Error enum in each module

This is exactly the path that my SNAFU library takes. It’s a procedural macro attached to an enum that generates the From, Display and Error traits for you.

This way, consumers of your code don’t need to know anything beyond the fact that you implement Error and any inherent public methods or traits you choose to expose. The implementation provided by SNAFU is internal to your code.

https://docs.rs/snafu/0.6.10/snafu/

shepmaster··on Rust is for Professionals
> into an `Option<&str>` from an `Option<String>`

`Option::deref` is a good choice here.

shepmaster··on A “Better C” Benchmark
Likewise, I don’t understand the disconnect. You have to have your list of dependencies on an internet connected machine, yes, but you already allowed that. You said:

> I could then go to an internet facing machine and run a cargo command

Copy over your Cargo.toml to that machine, run vendor, copy the vendor files back.

shepmaster··on A “Better C” Benchmark
A list of dependencies:

https://doc.rust-lang.org/cargo/commands/cargo-tree.html

Download all dependencies locally:

https://doc.rust-lang.org/cargo/commands/cargo-vendor.html

shepmaster··on Correct Git commits with git-autofixup
This process is what I think the article describes and what I’m curious if can be automated by magit itself.
← PreviousPage 4 of 9Next →