Hacker Newsnew | past | comments | ask | show | jobs | submit | estebank's commentslogin

> Any large Rust project I check has tons of unsafe in its dependency tree.

This is an argument against encapsulation. All Rust code eventually executes `unsafe` code, because all Rust code eventually interacts with hardware/OS/C-libraries. This is true of all languages. `unsafe` is part of the point of Rust.


Having a 3D engine does not a AAA make. The Witness is a beautiful looking game, but the amount of state and interactions it has to deal with is orders of magnitude less than GTA: San Andreas. It is closer to the complexity a Myst remake would have.

This reads to me like an argument for better refactoring tools, not necessarily for looser type systems. Those tools could range from mass editing tools, IDEs changing signatures in definitions when changing the callers and vice versa, to compiler modes where the language rules are relaxed.

I was thinking about C++ and if you change your mind about whether some member function or parameter should be const, it can be quite the pain to manually refactor. And good refactoring tools can make this go away. Maybe they already have, I haven’t programmed C++ for several years.

If you want something more conservative for error reporting, annotate-snippets is finally at parity with rustc's current custom renderer and will soon become the default for both rustc and cargo.

Will migrating to annotate-snippets change rustc/cargo formatting of errors in any way?

Also, in what sense it is more conservative?


The output will cause no user visible change.

It uses ASCII for all output, replaces ZWJs to have consistent terminal output in the face of multi codepoint emoji for two out of the top of my head.


If you have a good repro case, I'd appreciate a bug report. Bad diagnostics are considered bugs.

It's actually a classic and much-repeated case in Rust education:

    fn pick_first<'a>(x: &'a str, y: &'a str) -> &'a str {
        x  // We only actually return x, never y
    }
    
    fn main() {
        let s1 = String::from("long-lived");
        let result;
        {
            let s2 = String::from("short-lived");
            result = pick_first(&s1, &s2);
        } // s2 dropped here
        
        println!("{}", result);
    }
The error here is "borrowed value [pointing to &s2] does not live long enough". Of course it does live long enough, it's just that the constraints in the function signature don't say this usage is valid.

Thinking as a beginner, I think part of the problem here is the compiler is overstating its case. With experience, one learns to read this message as "borrowed value could not be proved to live as long as required by the function declaration", but that's not what it says! It asserts that the value in fact does not live long enough, which is clearly not true.

(Edit: having said this, I now realize the short version confuses beginners because of the definition of “enough”. They read it as “does not live long enough to be safe”, which the compiler is not—and cannot be—definitively saying.)

When this happens in a more complex situation (say, involving a deeper call tree and struct member lifetimes as well), you just get this same basic message, and finding the place where you've unnecessarily tied two lifetimes together can be a bit of a hunt.

My impression is that it's difficult or impossible for the compiler to "explain its reasoning" in a more complex case (I made an example at [0] [1]), which is understandable, but it does mean you always get this bare assertion "does not live long enough" and have to work through the tree of definitions yourself to find the bad constraint.

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

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


Would output along the following lines be an improvement?

   error[E0597]: `buffer` does not live long enough
   --> src/main.rs:63:21
      |
   59 |     let report = {
      |         ------ borrow later stored here
   60 |         let mut buffer = Vec::new();
      |             ---------- binding `buffer` declared here
   61 |         let ctx = Context {
   62 |             config: &config,
      |             ------ this field and `buffer` are required by `Context` to have the same lifetime
   63 |             buffer: &mut buffer,
      |                     ^^^^^^^^^^^ borrowed value does not live long enough
   ...
   68 |     };
      |     - `buffer` dropped here while still borrowed
   help: consider making different fields in `Context` have independent lifetimes
     |
   4 | struct Context<'a> {
     |                ^^
   5 |     config: &'a Config,
     |              ^^
   6 |     buffer: &'a mut Vec<u8>,
     |              ^^
   7 | }

Wow, yes, that would be great in this case. What would it do for pick_first?

It would be similar, pointing at the other argument. Neither case would have an applicable suggestion, just point in the right direction. I don't know if at the time we emit the error for `pick_first` we're able to evaluate the function's body to see if changing the lifetimes is feasible or not.

A hand saw, a table saw and a SawStop are all tools, but they have different characteristics even though they all are meant to cut the same wood.

Ada, C, and lisp are all tools, but they have different characteristics even though they are all meant to cut through the same problems.

...yes?

A tool is a tool. I didn’t realize I needed to spell it out.

Cloudflare used a tool, broke parts of the internet.


I feel like you're attacking a strawman here. Of course you can write unreliable software in Rust. I'm not aware of anyone who says you can't. The point is not that it's a magic talisman that makes your software good, the point is that it helps you to make your software good in ways other languages (in particular C/C++ which are the primary point of comparison for Rust) do not. That's all.

> The point is not that it's a magic talisman that makes your software good, the point is that it helps you to make your software good in ways other languages (in particular C/C++ which are the primary point of comparison for Rust) do not.

Citation needed.


> What user benefit are they promising?

From their README, they promise better error messages, extensions when relevant (example: --progress), and Linux, macOS, Windows and other platforms support. It is my understanding that the GnuWin32 coreutils were last updated in 2017 and had some subtle differences to regular GNU coreutils, so that's one set of users for whom there's clear benefit. Faster speed could be accomplished if architectural changes are viable, but for many of these tools they are IO-bound.


> uutils coreutils aims to be a drop-in replacement for the GNU utils. Differences with GNU are treated as bugs.

> Our key objectives include:

> Matching GNU's output (stdout and error code) exactly

> Better error messages

> Providing comprehensive internationalization support (UTF-8)

> Improved performances

> Extensions when relevant (example: --progress)

> uutils aims to work on as many platforms as possible, to be able to use the same utils on Linux, macOS, Windows and other platforms. This ensures, for example, that scripts can be easily transferred between platforms.

Experimenting with better error messages, as test-bed for extensions that might not be able to be tried or accepted in GNU coreutils (for technical, social or other reasons), and being able to use the same tools in all major OS are very reasonable divergences from GNU's project to "justify" its existence.

The project was originally just a learning project for someone who wanted to learn Rust by reimplementing tools that were small, not a moving target and useful. From there, it grew as it found an audience of developers interested in productionalizing it. There have been coreutils ports for the languages you mention (go-coreutils, pycoreutils, coreutils-cpp, etc.), they just didn't (yet?) hit critical mass. It is a harder sell for GC-based projects in this case because they are unlikely to ever be included as part of a dirtribution's base. Lets not forget that coreutils themselves are a rewrite of previously existing tools to begin with.


"unsafe" or `unsafe`? One is the general meaning of the word, the latter is "it invokes undefined-behavior".

As "unsafe". An example would be of how AMD GPUs some time ago didn't free a programs' last rendered buffers and you could see the literal last frame in its entirety. Fun stuff.

Could've been clearer above.


That is not a memory leak though! That's using/exposing an uninitialized buffer, which can happen even if you allocate and free your allocations correctly. Leaking the buffer would prevent the memory region from being allocated by another application, and would in fact prevent that from happening.

This is also something that Rust does protect against in safe code, by requiring initialization of all memory before use, or using MaybeUninit for buffers that aren't, where reading the buffer or asserting that it has been initialized is an unsafe operation.


It's a security hole. Rust doesn't prevent you from writing unsafe code that reads it. The bug wasn't that it could be read by a well conforming language, it was that it was handed off uninitialized to use space at all.

Fair, bad example.

Measuring success of a project against a bar that the project didn't set is like complaining that an F1 car is hard to park: that's not what it was meant to do.

Servo was meant to be a test-bed for new architectures that might or might not be usable by Firefox. It was never meant to become Firefox' new web renderer, and it wasn't until more recently and long after the Mozilla-pocalypse that a new team took over the project with a goal of productionalizing the project as a whole. Stylo, for example, was integrated into Firefox 57 and allowed for parallel CSS style calculation, an effort that was tried unsuccessfully multiple times in C++.


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

Search: