Brian Lovin
/
Hacker News
Daily Digest email

Get the top HN stories in your inbox every day.

kuon

I wrote a lot of rust, but after some years it still feels unproductive. I do a lot of zig now and I am like 10 times more productive with it. I can just concentrate on what I want to code and I never have to wonder what tool or what library to use.

I know rust gives memory safety and how important that is, but the ergonomic is really bad. Every time I write some rust I feel limited. I always have to search libraries and how to do things. I cannot just "type the code".

Also the type system can get out of control, it can be very hard to actually know what method you can call on a struct.

I still think rust is a great tool, and that it solves tons of problem. But I do not think it is a good general purpose language.

goku12

> but the ergonomic is really bad. Every time I write some rust I feel limited.

> But I do not think it is a good general purpose language.

Remember that this is not a sentiment that's shared by everyone. I use Rust for tasks that need anything more complicated than a shell script. Even my window manager is controlled from a Rust program. I say this as someone who has been programming in Python for nearly two decades now. At this point, I'm about as fast in Rust as I am in Python.

lumost

I tried to get into rust for many years, I'm now in a C/CPP job (after Java/Python/Ruby and other gigs). What I've come to understand is that Rust's lifetime model is very difficult to work with whenever you have a cyclic reference. In C/CPP the same holds, but you deal with it through clever coding - or ignoring the problem and cleaning up memory later. Java, and other GC'd languages just work for these structures.

While the Rust devs believe such cyclic references are rare - I think this speaks mostly to the problem domain they are focused on. Relational models are everywhere in Apps, they are often common in complex systems software like databases, and they are fairly rare in firmware/drivers/system code code.

There are a few patterns for dealing with cyclic references, but they all end up requiring either unsafe or a main "owner" object which you clean up occassionally (effectively arena allocation). Having now worked in C/CPP - the idea of having unsafe blocks sprinkled around the code doesn't bother me, and many C/CPP components have some form of arena allocation built-in. I just wish Rust learning resources would be more upfront about this.

lifthrasiir

> Relational models are everywhere in Apps, they are often common in complex systems software like databases, and they are fairly rare in firmware/drivers/system code code.

It's not like that you can't write relational models in the safe Rust. The only forbidden thing is a reference pointing arbitrary memory, which is typically worked around via indices and often more performant in that way. It is much rarer to find applications that need an absolutely random pointer that can't be hidden in abstractions in my opinion.

goku12

> I just wish Rust learning resources would be more upfront about this.

While beginner resources don't dwell too much upon cyclic references, they don't consider unsafe blocks as unusual. All the material I've seen say that there are certain domains where Rust's compile-time safety model simply won't work. What Rust allows you to do instead, is to limit the scope of unsafe blocks. However, the beginner material often won't give you too much details on how to analyze and decide on these compromises.

Anyway, compile-time safety checks (using borrow checker) and manual safety checks (using unsafe) aren't the only way to deal with safety. Cyclic references can be dealt with runtime safety checks too - like Rc and Weak.

oconnor663

Oh I just put up a blog post about this on Monday :)

https://jacko.io/object_soup.html

Agreed that I wish more beginner Rust books had a section about this. The pattern is quite simple, but it's hard for beginners who get stuck to realize that they need it.

worik

> While the Rust devs believe such cyclic references are rare -

They are.

I have not had to use cyclic references ever, except once doing experiments with fully connected graphs, that was very unusual

If you're doing a lot cyclic references Rust is not the right choice. Horses for courses

But are you sure you're using the best algorithm?

mjevans

What's a frequently encountered case for such cyclic loops? Without details I'm drawn to trying to break the cycle, either by promoting the shared state to a container object for the set, or by breaking it out into it's own object that multiple things can point at.

zabzonk

to be a bit pedantic, i assume the language you a referring to as CPP is actualy C++? cpp to me means the c (and c++) preprocessor.

PartiallyTyped

Surprisingly, I am faster in Rust than any other language. Something about my prior experiences just made it click just the right way.

I don't want to program in anything else anymore. I don't want to deal with obscure C++ error messages, C footguns and lack of ergonomics, I don't want to deal with abstraction hell of Java, or the poor person's typing that python has.

I have been programming in Python for the past 6 years, I know all sorts of obscure details, and with rust, I just don't need to think about all of those issues.

goku12

> Surprisingly, I am faster in Rust than any other language.

Not really surprising, given that you have C and C++ background. That's what I was trying to highlight. Rust isn't a confusing or unproductive language as many project it to be - if you have the conceptual understanding of what happens on the hardware. Especially about stack frames and RAII. If you know those, the borrow checker complaints will immediately make sense and you will know how to resolve them.

Add rust-analyzer (Rust's language server) to it, you get real-time type annotations and a way to match types correctly in the first attempt. In my experience Rust also helps structure your program correctly and saves a ton of time in debugging. All in all, Rust is a fast way to write correct programs.

pizza234

> At this point, I'm about as fast in Rust as I am in Python.

This is factually impossible.

For anything larger than (very) small programs, Rust requires an upfront design stage, due to ownership, that it's not required when developing in GC'ed languages.

This is not even considering more local complexities, like data structures with cyclical references.

goku12

> This is factually impossible.

How do you outright deny something as subjective as my personal experience? Besides, I'm not the only one in this discussion that made the same opinion.

> For anything larger than (very) small programs, Rust requires an upfront design stage, due to ownership, that it's not required when developing in GC'ed languages.

While GC'ed languages allow you to skip a proper initial design stage, it's a stretch to claim that it's not required at all. In my experience using Python, while the initial stages are smooth, such design oversights come back and bite at a later stage - leading to a lot of debugging and refactoring. This is one aspect where Rust saves you time.

> This is not even considering more local complexities, like data structures with cyclical references.

I'm not going to dwell on cyclical references, since there's another thread that addresses it. They point out a way to make it as easy in Rust as it is in GC'ed languages.

Meanwhile, the upfront architecture and data structure design isn't as complicated as you project it. Rust is mostly transparent about those - even compared Python. How well do you understand how Python manages Lists, dictionaries or even objects in general? I often find myself thinking about it a lot when programming in Python. While you need to think upfront about these in Rust, there's actually less cognitive overhead as to what is happening behind the scenes.

steinuil

That is also not shared by everyone. If you have written enough Rust to have internalized designing for the borrow checker, you don't have to spend much time in a design phase.

The only time I find I have to "fight the compiler" is when I write concurrent code, and you can sidestep a lot of issues by starting with immutable data and message passing through channels as a primitive. It's a style you have to get used to, but once you build up a mental library of patterns you can reasonably be as fast in Rust as you are in Python.

persnickety

> For anything larger than (very) small programs, Rust requires an upfront design stage, due to ownership, that it's not required when developing in GC'ed languages.

It's nearly the opposite. For larger programs in Python, you need an upfront design stage because the lack of static typing will allow you to organically accrete classes whose job overlap but interfaces differ.

Meanwhile, Rust will smack you over the head until your interfaces (traits) are well-organized, before the program grows enough for this to become a problem (or until you give up and start over).

How do I know? I'm stuck with some larger Python programs that became a mess of similar-but-not-interchangeable classes. RiiR, if I ever have the time.

bmacho

> This is factually impossible.

Factually it's not. It may be true that in a very very idealized thought-experiment, when someone has a perfect knowledge, never makes mistakes, doesn't have preferences, can type arbitrary fast etc, python needs fewer keystrokes, fewer keywords or such, thus is faster. Who knows. But in reality none of the assumptions above hold, also literally everything plays a much bigger role in development speed anyway.

thecodedmessage

You know what slows me down in Python? The fact that you need to actually go down a code path to make sure you’ve spelled everything right.

But nothing that Rust does slows me down because I’m used to it.

raincole

Of course it's possible. You just need to write Python very slowly :)

pg_1234

> For anything larger than (very) small programs, Rust requires an upfront design stage, due to ownership, that it's not required when developing in GC'ed languages.

Every language requires this (if you want robust code), most just let you skip it upfront ... but you pay dearly for doing so later.

sp0ck

I disagree. I have similar observation. With modern editors and Language Server that are giving immediate feedback writing strongly typed languages doesn't differ than writing python.

hiAndrewQuinn

I've finally set my mind to properly learning a new language after Python, Haskell, and typescript. I'm looking into Rust especially because of how I've heard it interoperates with Python (and also because it's maybe being used in the Linux kernel? Is that correct?).

tinco

Rust is an excellent follow up to those languages. It's got many influences from Haskell, but is designed to solve for a very different task that's not yet in your repertoire so you'll learn a ton.

And yes the Python interop is excellent.

PartiallyTyped

Linux kernel has support for rust userland drivers, and rust interops with python with pyo3.

Liskni_si

What window manager is that, may I ask out of curiousity?

goku12

Sway. And river, sooner or later. A single Rust program is used for setting up services (using s6-rc on Gentoo), shutdown, reboot, idle inhibit, etc.

JohnFen

It's interesting that you bring up Python. I find Rust unpleasant to program in -- and I also find Python unpleasant to program in.

Now I'm wondering about demographics. Are people who love Python more likely to love Rust as well?

ernst_klim

I agree. I feel far more productive in C and C++ than in Rust at that point.

Rust feels like totally missing the sweet spot for me. It's way too pedantic about low level stuff for writing higher level applications, but way too complicated for embedded or writing an OS. In the former case I would rather take a C++, Java, Haskell, OCaml or even Go, and maybe sprinkle some C, and in the latter case C in macroassembly mode is far more suitable.

I still have a feeling that original vision of Graydon Hoare (i.e. OCaml/SML with linear types, GC, stack allocations, green threads and CPS) would be a much better language.

api

The problem with C and to C++ is that it’s 2023 and the CVE list is still loaded with basic memory errors. These come from everywhere too: small companies and open source all the way up to Apple, Microsoft, and Google.

We as a profession have proven that we can’t write unsafe code at scale and avoid these problems. You might be able to in hand whittled code you write but what happens when other people work on it, it gets refactored, someone pulls in a merge without looking too closely, etc., or even maybe you come back two years later to fix something and have forgotten the details.

Having the compiler detect almost all memory errors is necessary. Either that or the language has to avoid this entirely. Rust is the former class unless you use unsafe, and the fact that it’s called “unsafe” makes it trivial to search for. You can automatically flag commits with unsafe in them for extra review or even prohibit it.

flohofwoe

I think nobody is arguing the need for static memory safety, just that the poor Rust ergonomics aren't a good tradeoff, especially for scenarios where C is useful. We need many more Rust alternatives that explore into different directions, Rust is already too big and "established" for any radical changes in direction.

pjmlp

It is, however those same companies aren't dialing full safe ahead knob either, hence why Microsoft just recently published a set of secure coding guidelines for C and C++.

https://devblogs.microsoft.com/cppblog/build-reliable-and-se...

ernst_klim

These are two issues, which a theoretically orthogonal but in practice not so much. These are known as soundness and completeness. A good talk on topic [1]

Rust will reject a lot of sound programs, and that's a huge performance hit. You are hitting the incompleteness wall with multiple mutable borrowing, closures in structures are a huge source of pain as well. And usually the answer from the community is "use handlers instead of pointers", but this gives you, surprise surprise, manual resource management alike that in C, and the compiler won't help much.

Of course this is all subjective but for me the ergonomics of Rust is far too bad. It's a good step in right direction along with ATS, but I really hope we could do better than this.

[1] https://www.youtube.com/watch?v=iSmkqocn0oQ

deterministic

I maintain very large C and C++ application and very rarely have any memory issues. Tools like Valgrind and Helgrind are excellent for finding and fixing problems. So switching to Rust is a very bad ROI.

khaledh

I gave Rust a few chances, and always came out hating its complexity. I needed a systems programming language to develop a hobby OS[1], and Nim hit the sweet spot of being very ergonomic, optional GC, and great interop with C. I can drop down to assembly any time I want, or write a piece of C code to do something exotic, but the rest of the system is pure Nim. It's also quite fast.

[1] https://github.com/khaledh/axiom

ostenning

Opposite experience for me. Writing Rust on embedded systems greatly improved my confidence and speed. When using C a small mistake often leads to undefined behaviour and headaches. Rust theres none of that - its been a game changer for me.

kuon

I was using rust on embedded, I moved to zig. Very happy with it as you can pass allocators around to use fixed buffer.

imtringued

When you are developing hardware on an FPGA, a lot of hardware bugs look like they have locked up the CPU and strangely enough, a lot of undefined behavior looks exactly like a hardware lockup...

0xfedbee

[dead]

anon-3988

I am curious what kind of code you are writing? Is it very low level or very high?

>I know rust gives memory safety and how important that is, but the ergonomic is really bad. Every time I write some rust I feel limited. I always have to search libraries and how to do things. I cannot just "type the code".

You don't have to search libraries and figure out how to do things in Zig?

dharmab

It's hard to describe, but in some languages, you spend a lot less time looking at reference docs and more time just naturally writing the solution. Lisp is a great example of that, if you get through the learning curve.

Buttons840

I suspect Zig libraries feel easier because they're doing easier things. I bet if you try to draw a triangle with the Vulkan API in Zig, you'll find yourself looking at the reference docs a lot.

kuon

Most of the time I can use my "general computer science baggage" to write the solution. At present, I do embedded and web business logic (wasm) where the UI is rendered by preact. For those two projects zig is working very well.

conradev

I agree with this general feeling, and it is hard to articulate

Rust forces you to figure out ahead of time where each bit or byte is going to go and on which thread and using which mutation scheme. I’m happy to play the game, but it feels tedious for anything short of a parser or a microcontroller.

It messes with my process because I like to get something working before I determine the best API structure for it

I can get 90% of the performance with Swift and it flows much more easily, even though Rust’s type system is more powerful.

Ar-Curunir

I’ve written plenty of Rust code in my life (easily more than 100kLOC, and I’ve really never worried about putting which bit where.

You can just clone and be on your merry way; you don’t need to worry about perf-related things if you don’t want to.

conradev

Those two sentences feel in contradiction of one another. You don’t need worry to about where the bits go, you just need to know to call a method to move the bits?

Swift makes every type implicitly copyable on the stack, including object pointers (through ARC), so you don’t have to clone. You can even pass functions as variables without any hoops.

I love lots of things about Rust, though, and will continue to use it for lots of things. Cross-platform things, for one!

pjmlp

I rather use compiled managed languages like Swift, D and C# instead, they provide enough low level coding knobs for C and C++ style coding, while being high level productive.

Would add Go to the list, but only when I really have to.

Nim and Crystal could be alternatives, but don't seem to have big enough communities, at least for what I do.

However I do agree with the conclusion, Rust is a great language for scenarios where no form of automatic memory management is allowed, kernels, specific kinds of drivers, GPGPU programming, as general purpose, there are more productive alternatives, equally safe.

hiAndrewQuinn

C# is underrated by the HN crowd, I find. I quite like how mid sized firms (100-1000 employees) use it.

tkubacki

I used to be .NET dev and don't agree. Couple of reaons:

1) Modern Java is almost as good as C# with some things I can't give up in Java (static imports => succint code, Groovy Spock => succint tests)

2) Kotlin is better than C#

3) JVM has much much bigger ecosystem (almost all the apache projects are JVM oriented) and default web framework is much less code to type (SpringBoot) is much more productiv

4) JVM has wider variety of langs

For those reasons IMHO if you are small-mid company (or startup) it's wiser to choose JVM.

eddtries

I've always found those sort of firms with C#, in my experience, have the best architected code. Proper domain-driven design, onion architectures, clean testable code... Some have legacy issues where they might not have the most cutting edge CI/CD pipeline or high automated test coverage, but the code itself can be very nice. I've never really experienced that level of consistency with a different language/company size.

leeman2016

C# is a lovely language to work with.

The only issue I have is with the .NET ... that is, building self-contained binaries to distribute.

For comparison:

* Hello World win-x64 binary self-contained in .NET 7 is around 70 MB

* The same for Go results in 1.2 MB

Edit: Missed 'trimming' in .NET, which would result in a binary of size around 11 MB in win-x64

hutattedonmyarm

Is it? Every time I see C# being mentioned here people agree how awesome it is. Not that I'm complaining, I love C#

Ygg2

C# is fine, but it feels like a slightly better Java, just without the huge ecosystem of libraries.

ninkendo

> compiled managed languages like [...] C#

I've been out of the windows development game for a long time, so I haven't used C# since it strictly required a VM... what's pre-compiled C# development like nowadays? Are there major caveats? If you can emit plain old binaries in C# with no runtime dependencies, that would make it a truly compelling language IMO.

And as another question, what's the cross-platform (mainly Linux) support like in AOT-compiled C#? If it's just as good as in Windows and emits plain executables, I would probably consider it the best place to start for any new project. (Something tells me it's not...)

pjmlp

C# supports AOT since forever, NGEN was present in .NET 1.0. Not many people used it, because it requires signing binaries and only supports dynamic linking, with a performance profile towards fast startup.

On Microsoft side the Singularity and Midori experiments used AOT.

They influenced the AOT toolchains for Windows 8 store apps with MDIL (Singularity/Bartok), and Windows 10 store apps with .NET Native (Midori/Project N).

Now there is Native AOT, which supports CLI and native libraries,.NET 8 extends that to EF and ASP.NET frameworks. For GUI applications, maybe only fully on .NET 9.

Mono AOT has had support for ages, being used on iOS, Android, and Blazor.

Finally there is IL2CPP and Burst compiler from Unity.

cztomsik

This. I have exactly the same experience, I can't believe how much I was able to ship with Zig and the code mostly feels like "done".

You can always improve it, but there's no need to. With Rust, I was never happy, even after 5 years, I was still thinking about better abstractions and implementing more traits, making it more generic, etc.

freilanzer

Why is there no need to improve Zig code but there is for Rust code? You'd need the same abstractions in Zig as well, no?

cztomsik

No, usually you don't. Rust has closures, iterators, generics, different traits for operator overloading, smart pointers, etc.

Zig doesn't have any of that. It's very interesting combination of low-level, predictable code, with meta-programming, where you get some of that abstraction back.

i.e. Zig does not have generics, but your function can return type, so generic list is just a function which returns a newly created struct.

epage

Can't speak for why Zig doesn't have a problem but Rust is cursed by its success: it lowers the barrier for improvement enough to entice you to always improve it.

push-f

> it can be very hard to actually know what method you can call on a struct

The rust-analyzer language server can autocomplete the available methods for a value.

n3storm

depending on the autocompleter feels like asking to code to Chatgpt to me.

push-f

I disagree, there's a big difference: rust-analyzer is deterministic and 100% accurate while ChatGPT is non-deterministic and hallucinates.

avgcorrection

Then you underestimate the power of ChatGPT by a facor of a million.

earthling8118

Furthermore you can use `cargo doc` to generate a documentation website that had everything you can do or you can use docs.rs for this. Whoever wrote this didn't embrace the tooling and just gave up.

chungy

Perhaps my biggest critique is that crates.io has no namespacing. Anyone can just claim a global and generic package name and we mostly have to deal with it (unless you avoid using the crates.io repository, but then you'll probably have more problems...). Some of these globally-claimed generic packages are not really the best package to use.

Maybe it was a reaction against the Java-style reverse DNS notation, which is verbose and annoying, but a more GitHub-style user/group namespace prefixing package names would have been the nice middle ground.

fawadasaurus

I did some analysis on crates.io to find the top name squatters. Then I did some calculations and found that the top name squatter created their crates at a rate of about one ever 30 seconds for a period of a week straight.

I send the analysis to the crates.io team and pointed that they have a no-automation policy.

They told me that it was not sufficient proof that someone was squatting those names. That's my problem with crates.io is that they have a clear policy and they don't enforce it so all the short/easy to remember names for crates are already taken and there is nothing you can do to get it.

j1elo

There's a HUGE gap between

> Using an automated tool to claim ownership of a large number of package names is not permitted.

And

- Hey, I found that someone created crates at a rate of about one every 30 seconds for a period of a week straight.

- That's not sufficient proof of squatting.

Whoever answered that, was either supporting the squatter or explicitly in favor of the practice. I cannot conceive that someone would get that evidence in their hands, and in their right mind think that the claim is bogus. Hell, I'd even be willing to suppress the squatter with evidence of one new crate created every 30 seconds for one hour!

The only reasonable conclusion to make is that they didn't really care. But then don't save face and claim that you do. That's hypocrisy.

nindalf

In July 2023 the crates.io team started asking for feedback around changing their policy around name squatting - https://rust-lang.zulipchat.com/#narrow/stream/318791-t-crat...

echelon

There's a secret effort in the Rust community to supplant Crates.io and create an entirely new package ecosystem with proper namespacing, security, and much better community.

Not naming names, but I know several people working to put Crates.io out to pasture.

There's a level of playing nice with them for the time being (eg. build reproducibility), but it's only KTLO.

Crates.io needs to die for Rust to thrive. They're a bungled, mismanaged liability. New code, new leadership.

jeroenhd

Crates.io doesn't need to die necessarily. It needs some competition as a wake-up call.

Once a better alternative is out there, crates.io will either wither and die or improve. If it matches its competition in terms of quality and reliability, everyone is better off. If not, the alternative solution will take over.

I'm eager for this crates.io alternative to land, assuming they don't break too many projects in their improvements.

solardev

Why does something like that need to be secret...? Isn't it in the community's best interest?

epage

I find this interersting as most namespacing solutions would need the cargo team involved and I've heard nothing about this.

awesome_dude

How secret is it now that you've posted on HN about it?

csomar

crates.io is fine.

ogoffart

> they have a no-automation policy

What's that? I have scripts that automate publishing of new release of my crates. And I think many projects have.

hobofan

Of course that's permitted.

What they stated was only regarding claiming new ownership over crate names:

> Using an automated tool to claim ownership of a large number of package names is not permitted.

serial_dev

Write some automated analysis that looks up popular packages on npm, pub.dev, rubygems, nuget. "Rustify" the package names. Add to it frequently used words, maybe popular names, etc. Then, write a script that creates an empty package, registers a name on crates.io every thirty seconds, and then you have about 20k package names after a week that nobody can use.

lolinder

> Maybe it was a reaction against the Java-style reverse DNS notation

I suspect it was less a reaction against anything and more just following the norms established by most other package managers. NPM, PyPI, RubyGems, Elixir's Hex, Haskell's Cabal... I'm having a hard time thinking of a non-Java package manager that was around at the time Rust came out that didn't have a single, global namespace. Some have tried to fix that since then, but it was just the way package managers worked in 2014/2015.

lucideer

> I'm having a hard time thinking of a non-Java package manager that was around at the time Rust came out that didn't have a single, global namespace

The implication here is that namespaces in package managers weren't a known concept. Outside Java, NPM - probably the biggest at the time - not only supported them but was actively encouraging them due to collective regret around going single-global in the beginning. Composer is another popular example that actually enforced them.

Not only was namespacing a known widespread option, with well documented benefits, it was one that was enthusiastically argued for within the Rust community, and rejected.

lolinder

NPM added namespaces in version 2, which was released in Sep 2014, just 2 months before cargo was announced. I don't remember anyone making a big deal about using scopes in NPM for several years after that, it was just there as an option. The announcement blog post of v2 only gives two paragraphs to scoped packages and explicitly frames the feature as being for enterprises and private modules [0]:

> The most prominent feature driving the release of npm 2 didn’t actually need to be in a new major version at all: scoped packages. npm Enterprise is built around them, and they’ll also play a major role when private modules come to the public npm registry.

My memory is that the industry as a whole didn't really start paying attention to the risks posed by dependencies in package managers until the left pad incident.

To be clear, I'm not saying that it was a good idea to not have a better namespace system or that they were completely ignorant of better options, just that they were very much following the norms at the time.

[0] https://blog.npmjs.org/post/98131109725/npm-2-0-0.html

rat87

What are the benefits?

stcroixx

Perl’s CPAN was around in the 90’s and all modules were namespaced.

lolinder

Fair enough. As I noted to another commenter, I'm not trying to say there was no prior art (if nothing else there was Maven), just that they were following the overwhelming majority of mainstream languages at the time.

jiggawatts

Yes, and all of those have had major security issues caused by their lack of foresight.

"We're pretending security is not an issue." has been the feedback every time this is raised with the Cargo team.

To be honest, it's turned me off Rust a little bit.

The attitude of "Rust is memory-safe, so we don't need any other form of security." is not a good one.

pcwalton

> "We're pretending security is not an issue." has been the feedback every time this is raised with the Cargo team.

Literally nobody has said this.

> The attitude of "Rust is memory-safe, so we don't need any other form of security." is not a good one.

Fortunately it's an attitude that nobody in the Rust project has!

muldvarp

> "We're pretending security is not an issue." has been the feedback every time this is raised with the Cargo team.

Do you have a specific link where I can read this response, because this is not at all the responses I have read.

selcuka

> I'm having a hard time thinking of a non-Java package manager that was around at the time Rust came out that didn't have a single, global namespace.

Technically not in the same category, but Docker Hub (2014) had namespaces.

thirdplace_

php's composer[0] in 2012 had package namespaces

[0] https://getcomposer.org/

lolinder

Sorta—it looks like they were mostly just using that system by convention until May 2015, when they finally become enforced [0]. Still, that's a good one that I hadn't thought of, and they at least had the convention in place.

[0] https://github.com/composer/packagist/issues/163#issuecommen...

jmyeet

I'm honestly astounded at how badly many languages have implemented dependency management, particularly when Java basically got this right almost 20 years ago (Maven) and others have made the mistakes that Java fixed. With Maven you get:

1. Flexible version (of requirements) specification;

2. Yes, source code had domain names in packages but that came from Java and you can technically separate that in the dependency declaration;

3. You can run local repos, which is useful for corporate environments so you can deploy your own internal packages; and

4. Source could be included or not, as desired.

Yes, it was XML and verbose. Gradle basically fixed that if you really cared (personally, I didn't).

Later comes along Go. No dependency management at the start. There ended up being two ways of specifying dependencies. At least one included putting github.io/username/package into your code. That username changes and all your code has to change. Awful design.

At least domains forced basically agreed upon namespacing.

beautron

> Later comes along Go. No dependency management at the start. There ended up being two ways of specifying dependencies. At least one included putting github.io/username/package into your code. That username changes and all your code has to change. Awful design.

"github.io/username/package" is using a domain name, just like Java. Changing the username part is like changing the domain name--I don't see how this is any worse in Go than in Java.

If you don't like that there's a username in there, then don't put one in there to begin with. Requiring a username has nothing to do with Go vs. Java, but rather is because the package's canonical location is hosted on Github (which requires a username).

I don't know why so many programmer's use a domain they don't control as the official home of their projects--it seems silly to me (especially for bigger, more serious projects).

cgh

Yes, #3 in particular is important for many large corps where one team develops a library that may be pulled in by literally thousands of other developers.

akoboldfrying

The dependency management side of Maven is great. OTOH, I was astounded to learn today that Maven recompiles everything if you touch any source file: https://stackoverflow.com/a/49700942

This was solved for C programs since whenever makedepend came out! (I'm guessing the 80s.)

(Bonus grief: Maven's useIncrementalCompilation boolean setting does the opposite of what it says in the tin.)

WatchDog

Maven and Java really don’t get enough credit for how well it’s dependency management works.

So many inferior dependency management systems for other languages have come along later, and learned nothing from those that came before it.

tootie

100% agree. It's unbelievable what a PITA it is dealing with pip or npm compared to Maven even 10 years ago. The descriptors could get convoluted but you could also edit them in an IDE that knew the expected tokens to make things happen.

symlinkk

What’s so hard about “npm install” and “package.json”. It’s dead simple

pjmlp

And NuGET, which was inspired on them.

baby

Is this a joke?

bsder

Not even remotely a joke.

The inverse-style domain name thing does a really good job of removing the whole issue of squatting from the ecosystem. You have to demonstrate some level of commitment and identity through control of a domain name in order to publish.

I would also say that this puts just enough friction so that people don't publish dogshit.

crates.io demonstrates quite clearly that you either have to go all the way and take responsibility for curation or you have to get completely out of the way. There is no inbetween.

speed_spread

Having tried Java and other languages, no, it's not a joke. Other than XML Maven got a lot of things right.

theendisney2

I think the correct approach is to do full-real-name_good-package-name it might not be practical but it would be legendary.

jrockway

URLs for packages makes a lot of sense. It works well in the land of Go. It also conveniently eliminates the need for the language to have a global packages database. Upload your package to example.com/your-thing and it's released! (You can, of course, still offer a cache and search engine if you want to.)

swatcoder

No, URL's don't make sense because your application shouldn't care where on the internet your dependency happened to be hosted when you integrated it. It's location has nothing to do with what it is.

By the time you're going to production, your vetted and locked dependency should be living in your own cache/mirror/vendored-repo/whatever so that you know exactly what code you built your project around and know exactly what the availability will be when you build/instantiate your project.

Your project shouldn't need to care whether GitHub fell out of fashion and the project moved to GitLab, and definitely shouldn't be relying on GitHub being available when you need to build, test, deploy, or scale. That's a completely unnecessary failure point for you to introduce.

Systems that use URL-identified packages can work around some of this, but just reinforce terrible habits.

taberiand

URLs are well structured and unique, with a sensible default - sourcing the file from the internet - and ubiquitous processes for easily mapping the URL to an alternative location.

I.e., when you're going to run the production build, the URLs are mapped to fetch from the vetted cache and not the internet.

I don't see any downsides to allowing them as a source, or making them the default approach

andiareso

Isn’t that why GOPROXY exists though? Not sure why you would need an internet connection. URLs don’t necessarily equate to the internet. Our internal and external packages are all locally hosted and work regardless of the internet being available.

awesome_dude

> By the time you're going to production, your vetted and locked dependency should be living in your own cache/mirror/vendored-repo/whatever so that you know exactly what code you built your project around and know exactly what the availability will be when you build/instantiate your project.

In the Go world this would be "vendored" dependencies, that is, the dependencies are within your source tree, and your CICD can build to its hearts content with no care in the world about the internet because it has the deps.

The URL is useful for determining which version of a specific project is being used - "Oh we switched to the one hosted on gitlab because the github one went stale"

The advantage of using gitlab, or github, or whatever public code repository is that you get to piggy back off their naming policies which ensure uniqueness.

But, at the same time, there's no reason that the repo being referred to cannot be in house (bob.local) or private.

Having said all of that, the Go module system is a massive improvement on what they did have originally (nothing) and the 3rd party attempts to solve the problem (dep, glide, and the prototype for modules, vgo), but it's not without its edge cases.

jowea

Isn't that just delegating the problem? URL dependencies do not replace what crates.io does, and a modern language will still want something like it. You'd just end up with most every dependency being declared as crates.io/foo.

kevincox

URLs form a nice global namespacing system. But yes, I agree that it should be possible to actually get the source from anywhere.

Basically the URL of a package name should be primarily the ID, not the locator (even if it is used for location by default).

kitd

You can use the `replace` option in the Go mod file to redirect your dependency references elsewhere if you need to.

jrockway

It worked for the rest of the Internet.

jeroenhd

You can, though. From a random Cargo package I have downloaded to my computer:

    [dependencies]
    uniffi = { git = "https://github.com/mozilla/uniffi-rs" }
    
You can also specify revision/branch/etc.

Alternatively, you can do:

    [registries]
    maven = { index = "https://rust.maven.org/git/index" }
    [dependencies]
    some-package = { index = "maven", version = "1.1" }
    
Obviously Maven doesn't host any Rust crates (yet?), this is just a theoretical example. Very few projects bother to host their own registry, partially because crates.io doesn't allow packages that load dependencies from other indices (for obvious security reasons). The registry definition can also be done globally through environment variables: CARGO_REGISTRIES_MAVEN="https://rust.maven.org/git/index". Furthermore, the default registry can be set in a global config file.

In theory, all you need to do is publish a crate is to `git push upstream master`, and your package will become available on https://github.com/username/crate-name (or example.com/your-package if you choose to host your git repo on there).

Personally, I don't like using other people's URL packages, because your website can disappear any moment for any reason. Maybe you decide to call it quits, maybe you get hit by a car, whatever the reason, my build is broken all of the sudden. The probability of crates.io going down is a lot lower than the probability of packages-of-some-random-guy-in-nebraska.ddns.net disappearing

estebank

It doesn't help with the failure mode of dependencies disappearing, which forces people that care about it to vendor, which in turn brings its own set of issues.

alpaca128

Cargo does support URLs to git repos for dependencies. But crates.io is the official platform and almost every search I do on it returns at least one generically named entry with an empty repository that someone snatched away and never used.

corytheboyd

I don’t work with rust on the regular, but this is so annoying with package repositories in general. No don’t use http-server, it’s bad, instead you have to use MuffinTop, it’s better. And then you just have to know that. The concept of sanctioned package names would be interesting, but probably chaotic in practice as the underlying code behind this sort of alias changes over time. This will remain a part of being a domain expert in any given ecosystem forever I think, hooray!

ReactiveJelly

(You probably agree with me but I'm going to just write one big comment instead of replying to every slightly incorrect comment in the thread)

Naming things really is one of the hardest problems. This crates thing is a special case of Zooko's Triangle: https://en.wikipedia.org/wiki/Zooko%27s_triangle

Crates.io names are human-meaningful and everyone sees the same names, but it's vulnerable to squatting, spamming, and Sybil attacks.

You could tie a name to a public key, like onion addresses do, but it's unwieldy for humans. (NB, nothing stops you from doing this inside of crates.io if you really wanted)

You could use pet names where "http-server" and "http-client" locally map to "hyper" and "reqwest", but nobody likes those, because they don't solve the bootstrap problem.

It's a problem with all repos because when you say "http-server should simply be the best server that everyone likes right now", you have to decide who is the authority of "best", and "everyone", and "now". Don't forget how much useless crap is left in the Python stdlib marked as "don't use this as of 2018, use the 3rd-party lib that does it way better."

So yeah... probably will be a problem forever. As a bit of fun here are some un-intuitive names, and my proposed improvements:

- Rename Apache to "http-server"

- Rename Nginx to "http-server-2"

- Rename Caddy to "http-server-2-golang"

- Rename libcurl to "http-client"

- Rename GTK+ to "linux-gui"

- Rename Qt to "linux-gui-2"

- Rename xfce4 to "linux-desktop-3"

Then you only need to remember which numbers are good and which numbers are bad! Like how IPv4 is the best, IPv6 is okay, but IPV5 isn't real, and HTTP 1.1 and 3 are great but 2 kinda sucked.

Very simple. If a company as big as Apple can have simple names like "WebKit", "CoreGraphics", and "CoreAudio" then surely a million hackers competing in a free marketplace can do the same thing.

Conscat

I think if you get three to five developers who are enthusiastic about X language, their collective knowledge will select good packages.

littlestymaar

> Perhaps my biggest critique is that crates.io has no namespacing. Anyone can just claim a global and generic package name and we mostly have to deal with it (unless you avoid using the crates.io repository, but then you'll probably have more problems...). Some of these globally-claimed generic packages are not really the best package to use.

This is true that with no namespace anyone can end up squatting a cool name, but with namespace you end up in an even worse place: no-one end up with the cool names, and it makes discoverability miserable, because instead of having people come up with unique names like serde, everybody just name their json serialization/parsing library json and user now needs to remember if they should use "dtolnay/json" or "google/json" (and remember not to use "json/json" because indeed namespace squatting is now a thing), and of course this makes it completely ungoogleable.

We've had the namespace discussion for hundreds of time in the various Rust town squares, and the main reason why we still don't have namespace is because it doesn't actually answer the problem it's supposed to address and if you dig a little bit you realize that it even makes them worse.

Having a centralized public and permissionless repository opens tons of tricky questions, but namespaces are a solution to none of them.

pdimitar

> everybody just name their json serialization/parsing library json and user now needs to remember if they should use "dtolnay/json" or "google/json"

What's the problem with that? You will have to explicitly state intention which is always a good thing.

> and of course this makes it completely ungoogleable

You are aware that just pasting username/projectname gives you exactly what you are looking for in the several top search engines, correct?

> We've had the namespace discussion for hundreds of time in the various Rust town squares, and the main reason why we still don't have namespace is because it doesn't actually answer the problem it's supposed to address and if you dig a little bit you realize that it even makes them worse.

OK, if you say so. Is this worse state of things documented somewhere?

yencabulator

> and of course this makes it completely ungoogleable

Do you really think nobody has ever been able to google a Go import path? Some of these arguments are ridiculous.

SCLeo

If you have a namespace, can't people just globally-claim namespaces instead? like serde/serde or something similar. I feel if you really don't want people claim whatever they want, you have to do the Java package style where namespaces are tied to domain names.

pjmlp

While Java made that notation famous, it was already used in NeXTSTEP and Objective-C, hence why you will see this all over the place in macOS and derived platforms, on configuration files and services.

veber-alex

> It also looks like (soon) you’ll finally be able to configure global lints for a project. Until now, you had to hack your solution to keep lints consistent for projects.

> I questioned my sanity every time I circled back around to the Clippy issue above. Surely, I was wrong. There must be a configuration I missed. I couldn’t believe it. I still can’t. Surely there must be a way to configure lints globally. I quadruple-checked when I wrote this to make sure I wasn’t delusional.

You create a .cargo/config.toml in the workspace root so it covers all your crates.

inside the file:

  [build]
  rustflags = ["-Wclippy::lint_name_to_warn", "-Dclippy::lint_name_to_deny"]
The only limitation is that rustflags are not additive, so if you have other sources of rustflags like the RUSTFLAGS environment variable it will overwrite this setting.

mplanchard

We just have a script that adds the lints to any lib.rs or main.rs files that runs on commit. ezpz

JohnFen

I'm learning Rust because it seems clear that it's going to be important professionally. I wish I loved it, I really do. I see the benefits. But, at least so far, it's one of the most unpleasant languages I've used. I keep hoping that as I gain proficiency, I'll stop disliking it, but as I climb higher on the learning curve, I'm not really warming to it.

It's fine. It won't be the only language I'll be proficient in while being averse to it at the same time. But I heard so many people proclaiming their love for it that I expected to enjoy it, too.

goku12

As a counter example, I love programming in Rust. Fighting the borrow checker ended a long time ago. Even the errors are rare these days, except in cases I trigger them in order to examine the types. Rust compiler also seems to have improved in accepting broader cases that are valid.

For me, the key to understanding the borrow checker was understanding the underlying memory model. Rust memory model is the same as that of C, with some extensions for abstractions like generics. The borrow checker rules seem arbitrary at first. But it's deeply correlated to this memory model. The real value of the borrow checker is when I trigger it unintentionally. Those are bugs that I made due to lapse in attention. What scares me is that another language like C or C++ might simply accept it and proceed.

Yet another pleasant side effect of Rust's strict type system and borrow checker is that they gently nudge you to properly structure your code. I can say for certain that Rust has improved my code design in all the languages that I use.

atemerev

How do you do anything dynamic/cyclic? Like, graphs that need to be updated in runtime?

goku12

My experience is that most programs dealing with ordinary problems don't need such complicated data structures. In cases you do, Rust has a few options:

1. Use Rust's runtime safety checks, using Rc, Weak, RefCell, etc. It will be as ergonomic as GC'ed languages (no productivity loss). While this has a runtime performance penalty, it will still be mostly comparable to other languages. This works for most use cases.

2. If you need the last ounce of performance, just drop all automated safety checks and do it manually using unsafe. Even if you make a mistake, your debugging will be limited to the unsafe blocks. This approach isn't unusual in Rust.

3. Use either the standard library or something on crates.io that does what's given in 2. Rust's generics make it easy.

This resource deals with these topics in-depth: https://rust-unofficial.github.io/too-many-lists/

xtremegoose

Use one of the many libraries that have safe abstractions over unsafe code? I don't know why people think you need to roll your own? I guess that's just what people do in c/c++, doesn't seem very productive...

https://docs.rs/petgraph/latest/petgraph/

peterashford

I've tried several times to get into Rust and keep bouncing off it. It just feels so non-ergonomic.

TillE

The lack of default/named function arguments is what still gets me. It's such an absolute basic programmer ergonomics feature shared by most popular languages; even C++ has had defaults since forever.

metaltyphoon

It would have been nice for Rust to have Default/named/optional function arguments because the proliferation of slightly differently named functions that do the same thing would go away.

HKH2

Well, what do you dislike about it?

JohnFen

Good question! There's no single thing, I think, and the things I dislike about it aren't even really technical criticism or the like. They're more... aesthetic? I find the syntax unpleasant, for instance. It's extremely opinionated and a few of those opinions are ones I disagree with.

Also, just generally, it tends to make even simple things pretty complex. I understand why and am not really objecting to that, but it does make using it a bit like running with lead shoes.

I expect that the latter part may get better as I use it more (but perhaps not -- there are other languages I'm fully competent in but dislike for similar reasons).

GuestHNUser

I share your sentiment about Rust. Fine language for sure, I just don't enjoy programming in it. Simply put, I don't like using the abstractions that the language encourages.

For what it is worth, I found pursuing Zig to be a breath of fresh air. It's a promising alternative in the same space as C (and also Cpp and Rust). Checkout Andrew Kelley's introduction to the language.

xtremegoose

Which opinions do you disagree with? For transparency, I tend to find myself agreeing with almost all of its models/abstractions.

fawadasaurus

The beatings will continue until morale improves.

two_handfuls

Find a tutor or mentor. It can make a huge difference.

JohnFen

In what way? I'm not really struggling much with the mechanics or logic of it...

mamp

ChatGPT has helped me survive learning Rust.

63

That's the opposite of my experience. The Rust book helped a lot and going to chatGPT for additional questions usually ended up with bad answers.

dilippkumar

> After two decades of JavaScript and decent experience with Go, this is the most significant source of frustration and friction with Rust. It’s not an insurmountable problem, but you must always be ready to deal with the async monster when it rears its head. In other languages, async is almost invisible.

I am a former C and C++ programmer who lived calling into pthread almost every week for a decade. I use async rust everywhere now.

I don’t get the hate that async gets. In my opinion, everyone should be using async for everything. Including stuff that’s seemingly single threaded “simple” stuff.

galangalalgol

I think it is the infectiousness of it. Especially in embedded or wasm contexts, the predominant async may not be the async you want. Wasm being the author's use case would definitely have provided a different perspective.

Similarly, I find tasks that use or reuse large buffers to avoid the performance hits from allocation, often benefit from old fashioned thread pools. Bump or shard allocators can make this work ok, but in the cases where you are cpu bound on tight loops of vectorizable operations, thread pools perform better. Async is a good tool, but there are contexts it isn't optimal for.

badrequest

Recently tried writing some async Rust to compare the error handling when nested async calls are made to how errors are handled in Go, and it seemed like the trivial example I was trying to write up simply couldn't be done without involving Tokio. That barrier simply doesn't exist in Go, or C#, or Typescript.

For instance, you apparently cannot `await` in the main function without a decorator you import from, you guessed it: Tokio.

lmm

You need an async runtime to run async code yes, and Rust's isn't built in. Why does that matter though? Rust has a decent package manager; add the dependency and move on.

cchance

Why are you trying to avoid Tokio lol, tokio is the defacto async runtime in rust, saying you're trying to avoid it is like saying you're trying to avoid async while writing async, somehow people act like if they merged tokio into std and instead of #[tokio::main] or whatever you had to do #[async::main] it would somehow be better.

Once you stop fighting the fact that tokio = async rust for 99% of cases, things are quite smooth.

galangalalgol

Tokio simply doesn't meet the ise case of some people doing wasm, and many people doing embedded. That isn't a huge deal, just don't use it right? Except many otherwise usable crates seem to adopt tokio unnecessarily.

yencabulator

If Tokio was the final answer, https://github.com/tokio-rs/tokio-uring/ wouldn't exist.

cmrdporcupine

Your "lol" is entirely inappropriate.

ilaksh

The problem that I am running into at the moment is that a few things like the rhai Engine aren't Send and I am trying to use them in an async closure. What GPT-4 suggested was creating a tokio Runtime inside the thread and then block_on(). I will try it tomorrow. (This is the first significant Rust project for me.)

codys

The answer here is almost certainly one of these:

1. Fix rhai::Engine so it is Send (if !Send is unintentional)

2. Use tokio::spawn_blocking or normal threads to run the rhai::Engine bits

3. Don't hold the rhai::Engine across an await point.

Which one depends on rhai Engine details and what you want to accomplish.

Doing a block on inside a new thread seems unlikely to do anything useful (unless there's some undisclosed detail that makes it reasonable).

I encourage you to ask about this in the rhai repo in a discussion or issue.

ilaksh

1. Not applicable unless it is absolutely required, and not something I will consider until I have exhausted other options, since there is a reason they have not made it Send already. It will likely be quite complex and enlarge the scope.

2. I am using a normal thread but when I make it async the compiler wants Send.

3. The await point is in code that uses the engine. I am not sure there is another good option, since I need to use an API that has several libraries all of which are async.

The block_on is to allow the tokio Runtime created in that thread to execute/poll it

So, thank you for your input, I will test out the suggestion that I mentioned above, and then maybe look into spawn_blocking if that doesn't work.

brochington

From the rhai Engine docs: `Currently, Engine is neither Send nor Sync. Use the sync feature to make it Send + Sync.`

Measter

According to the documentation[0], enabling the "sync" feature should make the Engine Send+Sync.

From a quick look at the source, it looks like it switches between using Rc/RefCells and Arc/RwLock.

[0] https://docs.rs/rhai/1.16.2/rhai/#enable-special-functionali...

cchance

It being !Send means its not safe to be sent lol, that's down to the way they implemented rhai engine not rust. It's just that rust catches that it's not safe to send because of the trait bounds.

jgilias

You might be able to use the LocalSet (https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html) to run !Send futures on a single thread.

Alifatisk

> In my opinion, everyone should be using async for everything. Including stuff that’s seemingly single threaded “simple” stuff.

I learned the opposite, that programmers tend to make everything unnecessarly async when it's not needed causing more complexity and mental load.

undefined

[deleted]

ridiculous_fish

Can you please elaborate on this? What's an example of a seemingly single threaded simple task where you would turn to async?

Does "use async rust everywhere now" imply "use tokio everywhere now?" Honest question.

pornel

The way closures made it easy to encapsulate code and state in an object you can run at any time, async encapsulates code and state for ability to run and pause or cancel at any time.

This is handy for I/O that can be interleaved and cancelled. You can (ab)use it for other things like generators or various DIY multitasking operations. It can also be a state machine generator (e.g. AI of actors in a game).

But I think OP just meant async for typical networking and DB interfaces. And yes, this usually implies the Tokio dependency.

undefined

[deleted]

sunshowers

Programming in Rust is really not like being in an abusive relationship. The compiler is trying to help out as much as possible, especially since rustc has the best error messages in the world.

emporas

The OSes want programmers to handle resources correctly, and the Rust compiler makes that task a breeze. We are in a more abusive relationship with our OSes, than the Rust compiler. How about the hardware? Doesn't that need to run assembly in a correct way? That counts as an abusive relationship as well.

Rust's error messages are one of a kind. There no other compiler which comes even close.

As a side note, i used latex lately, it's error messages are horrendous. What a nightmare to figure out what's wrong by inspecting the error.

Aerbil313

Rustc is so good I learnt a lot about the language by just reading errors.

CGamesPlay

> The compiler is trying to help out as much as possible, especially since rustc has the best error messages in the world.

Generally good, but man do I hate how any error in my async function causes every recursive call site to generate an error about how the Future is no longer Send and Sync. Literally an entire console scrollback of errors with the actual syntax error buried somewhere in the middle.

estebank

I believe this is in our radar and waiting on the new trait solver, but just in case if you have a repro I would appreciate a ticket to improve the diagnostic.

CGamesPlay

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

In this case, the first two errors are clear. The next 3 provide multiple, redundant context blocks. The upshot is that this simple example results in rustc printing 11 lines of useful error messages and 86 lines of useless messages. Add to that the fact that the useful error messages need not be at the top or bottom of the error list, they can be anywhere inside of it, depending on the declaration order.

Tade0

Rust's compiler is the first one I've seen to use the word perhaps.

My main gripe is that I still don't fully comprehend lifetimes and the compiler can't really help me every time, because it (understandably) errs on the side of caution.

estebank

Whenever you see weasel words like that, it means the compiler knows that the issue you encountered could be what it is saying but the necessary metadata to figure out for sure is inaccessible to it. It's the problem with classical stage-oriented compilers. A compiler designed for diagnostics from the beginning would end up looking like a plate of spaghetti where you can call type checking from the parser, to give an example.

awused

>It's the problem with classical stage-oriented compilers. A compiler designed for diagnostics from the beginning

I'm not sure any compiler, even one "designed for diagnostics," can gather the "necessary metadata" from inside my brain based on my original intentions. If the compiler could unambiguously interpret what I meant to write, it wouldn't have needed to fail with a compilation error in the first place. Whenever I see something like "perhaps" or "maybe" in a compilation error, the information just didn't exist anywhere the compiler could possibly get to, and it's just a suggestion based on common mistakes.

fawadasaurus

[flagged]

estebank

As one of the main people working on Rust compiler diagnostics, I find this comparison beyond distasteful. The tooling is not capricious in its restrictions and we go out of our way to make it communicate to people with as much empathy and support as possible.

latency-guy2

There's a clear distinction between someone who writes the code and everyone else in the world who works with the outputs of your code.

Doesn't feel that way to me.

sunshowers

This really is an inappropriate comparison. Can we be serious for a bit? A professional-grade tool providing professional-grade feedback is not remotely like an abusive or even turbulent relationship.

civilitty

Besides, it's mostly the programmers doing the abusing.

People do sick things with lifetimes and generic associated types. Sick, sick things.

johnnyanmac

More like "this submission has errors check your work". That's the job of any compiler, Rust was made to be especially strict.

If you want sugarcoating or simply rapid prototyping I'd use a dynamic or scripting language instead.

tantalor

> I started writing tests in Rust as I would in any other language but found that I was writing tests couldn’t fail.

This is a common refrain in C++ testing: if it compiles then it's probably correct.

> Rust has accounted for so many errors that many common test cases become irrelevant

In practice, if you think this way I think it's a sign that you aren't testing the right things in those other languages. You should be testing business logic, not language stuff.

If you look at your test code and think "I would test this in JavaScript, but I don't need to do that in Rust" then just delete the test.

adastra22

> This is a common refrain in C++ testing: if it compiles then it's probably correct.

I’ve heard this in Haskell and in Rust. I’ve never heard it applied to C++…

ModernMech

I think they meant Rust? Rust definitely has that property to an extent; C++ is so far from it it’s not even funny.

jeremyjh

I’ve definitely experienced it in Haskell and Rust. I can believe some C++ could be that way, but I’ve never experienced it, but then again those projects didn’t have useful tests either. I think with C++ a lot of this depends on domain and the quality of the code and libraries.

camkego

That used to be a joke at many places I worked at with large C/Cpp code bases. Always said tongue in cheek.

PoignardAzur

I've experienced it a bit in C++, especially after coming back from JS. But not to the degree of Rust.

xhainingx

It's truer in C++ than java or similar languages ime since java relies more on exceptions, but it's certainly not as true as Haskell or rust

Capricorn2481

No it isn't lol. C++ compiles all the time with bugs.

Skunkleton

Is it true enough in c++ to be useful though?

Sharlin

A null pointer exception is a bug that breaks business logic. There's no "business logic instead of language stuff" because the language stuff is the foundation that business logic rests on. If you don't test against failure modes, what's even the point in testing?

maxbond

To close the loop, Rust doesn't include a `null` type and you wouldn't encounter something comparable in idiomatic Rust (because you'd be using eg Option::map to handle None cases gracefully), so this is a class of test that would be common in Java and C that is close to irrelevant in Rust.

tialaramex

To be more specific, Rust does have among other things:

std::ptr::null() - an actual null pointer, probably the zero address on your hardware, and this isn't even an unsafe function. On the other hand, you won't find many uses for a null pointer so, I mean, congrats on obtaining one and good luck with that.

std::ptr::null_mut() - a mutable null pointer, similarly unlikely to be of any use to you in safe Rust, but also not an unsafe thing to ask for.

But, these are pointers, so they're not values that say, a String could take, or a Vec<String> or whatever, only actual raw pointers can be null.

benibela

Still someone might call unwrap on an Option and then run into some kind of null pointer exception

dgb23

If an exception breaks business logic, then you can test the code by testing business logic.

How do null pointer exceptions arise?

Inconsistent data: you have code paths that implicitly assume invariants. Trivial cases like “this field is not provided” and more complex ones like “these fields have a specific relationship”.

You can often move those assumptions into the data structure in any language.

Then your tests become a matter of generating and transforming data from a holistic perspective instead of micromanaging individual code paths.

rightbyte

A null pointer exception is a free runtime check. I really don't understand the fuss about null. The "most expansive design mistake in computer science" and whatever.

Sharlin

Free runtime check of what? The point is that if nulls don't exist, there is no need for a "check". Runtime or otherwise. If I write a type that models Foo, I want it to model Foo and not "Foo or null". If I want to model "Foo or no data" then I use a separate type that makes my intention clear (in Rust spelled `Option<Foo>`). Languages where nothing is precisely Foo and everything is "oh by the way this is only possibly Foo" are deeply, deeply flawed.

tialaramex

There is surely something wonderful about the kind of C++ programmer who figures that, since their unusable broken garbage compiled it's probably correct.

Remember unlike most languages you'd be familiar with C++ has IFNDR, which has been jokingly referred to as "False positives for the question: Is this a C++ program?". A conforming C++ compiler is forbidden from telling you in some† unknown number of cases that it suspects what you've written is nonsense, it just has to press on and output... something. Is it a working executable? Could be. Or maybe it's exactly like a working executable except it explodes catastrophically on Fridays. No way to know.

† The ISO standard does identify these cases, but they're so vague that it's hard to pin down everything which is covered. My guess is that all or most non-trivial C++ software is actually IFNDR these days. Just say No to the entire language.

lmm

> A conforming C++ compiler is forbidden from telling you in some† unknown number of cases that it suspects what you've written is nonsense, it just has to press on and output... something.

It's not quite that bad - a conforming C++ compiler is permitted to error out and not compile the program. It just doesn't have to.

tialaramex

Many of the cases of IFNDR are semantic constraints, especially in C++ 20 and beyond. As a result of being semantic constraints it's generally impossible to diagnose this with no false positives. The ISO standard forbids such false positives so...

_gabe_

Can you give an example of non-C++ code that a modern compiler (MSVC, clang, g++ or something) successfully compiles with no diagnostics? I’m genuinely curious. If not, this just sounds like more C++ FUD because the spec doesn’t define everything under the sun and allows a certain amount of leeway to compilers for things like emitting different error diagnostics.

LegionMammal978

Consider:

  #define _FOO
  int main() {}
Per the C++ standard ([lex.name]/3), this program is ill-formed:

> In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required. [...] Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.

Thus, the compiler theoretically has the liberty to emit whatever it wants for this program.

Neither GCC nor Clang produces a warning under -std=c++20 -Wall -Wextra (Clang only produces a -Wreserved-macro-identifier under -Weverything), and MSVC doesn't produce a warning under /std:c++20 /Wall.

In practice, most examples of ill-formed programs where compilers issue no warnings occur with discrepancies between different source files that are linked together; e.g., declaring a function as inline in one file but non-inline in another, or declaring a function with two different sets of default arguments in different files, or defining the same non-inline variable or function in different files, or defining the same inline function differently in different files.

throwaway914

I feel like Rust finally broke the idea that programmers should be in complete control and completely conscious of everything the compiler is doing. It hasn't been that way in decades, compilers are freaking magic. But Rust undid a lot of that with borrowing. People became comfortable with the compiler knowing better than them. I just wish we could relax further: We should never be explicitly iterating forward over a collection unless we need this behavior for the algorithm. Things should be implicitly parallel. Etc. Give me rusty bash.

pornel

That seems entirely opposite to my Rust experience.

Rust is quite transparent in what it does, and is very conservative with compiler magic. The language doesn’t do heap allocations, doesn’t do reference counting, it doesn’t even have implicit numeric type conversion. It won’t implicitly copy types that did not ask to be implicitly copyable, and even that is only legal for types that can be copied with a simple shallow memcpy.

Rust uses zero-cost abstractions all over the place, which means it’s predictable what code they will compile to, and that will be typically something simple. Std types have well-known basic layout, so you know that e.g. iteration over a Vec is going to be a loop incrementing a pointer, and there’s no implicit parallelism.

Describing borrowing as “compiler knowing better than the programmer” is a weird way of looking at it. Borrowing is like type checking. You declare a type to be temporary, and try to use it as long-lived, you get an error. It’s the same as if you declare function to return struct Foo, but returned struct Bar instead. Yes, compiler “knows better than you”, because you just wrote a bug.

Borrowing still compiles to direct pointer usage without a GC (it’s literally guaranteed to be identical to a C pointer in C ABI structs and functions), and you can override lifetimes with unsafe if you think know better than the compiler.

alpaca128

> We should never be explicitly iterating forward over a collection unless we need this behavior for the algorithm. Things should be implicitly parallel

There is already a crate providing parallel iterators. You just rename the iter() call and that's it. I don't agree it should be implicit though.

ModernMech

It’s called Rayon for anyone who wants to know.

oconnor663

I think "not in control of what the compiler is doing" is overstating things a little bit. In some ways, Rust gives the programmer more control than C does. For example, Rust has standardized support for inline assembly, but inline assembly in C relies on vendor-specific extensions.

But to your point, the convenient defaults are very different. Unsafe typecasts require a lot of ceremony and careful thought in Rust, and they have to follow more rules than in C. In particular, references are all effectively "restrict" in Rust, and it's really easy to screw that up when you do unsafe cast from raw pointers to safe references, which is a big incentive not to write code like that if you have a choice.

jeremyjh

> but inline assembly in C relies on vendor-specific extensions.

Whereas Rust is a standard with multiple implementations?

oconnor663

I should've said "stabilized" instead of "standardized" to avoid stepping on this conversational landmine. But an important practical difference is that Rust supports inline assembly on Windows. (Correct me if I'm wrong, but I think MSVC mostly does not support inline asm.)

j-pb

As someone who does a lot of unsafe rust, including tagged pointer foo, I strongly disagree.

If anything I want more explicit control, alleviated with an even more expressive type system. Ideally rusts type system would just be a prolog variant imho.

throwaway914

Anyone that can reliably and off-the-cuff do all of these can have complete control back:

https://en.wikipedia.org/wiki/Category:Compiler_optimization...

Otherwise we should trust compilers to know better.

charcircuit

No thanks. Rust compile times are already too slow.

HKH2

> Give me rusty bash.

Bash with types (especially floats), fewer edge cases, functions with explicit parameters, simple command line flags...

AgentME

You're skipping the most needed improvement of all: fixing the issue where scripts with unquoted variables will seem to work until they contain a space. Almost every single bash script of more than a dozen lines I've seen outside of large open-source projects fails if a user puts a space in a file path, because the programmer didn't understand the insanity of variable quoting rules! I don't know of any programming language with a more common major footgun.

HKH2

Shellcheck does cover that. I think the bigger problem is people programming without warnings (in any language).

edit: Although I can see that if they hadn't made string splitting the default, it would have saved a lot of headaches.

mike_hearn

We use a Kotlin scripting variant for our own shell scripting needs at Hydraulic, it's pretty nice because we've joined it with a lot of internal APIs that make working with the filing system and network easy. It fits all those requirements and more (you can declare flags easily at the top level, it has built in progress tracking for slow operations etc).

The Kotlin type system is comparable to Rust without the borrow checker. It has non-null types, generics, etc.

It's not really a "product" per se but there's an old version for download and some lightweight docs here:

https://hshell.hydraulic.dev/

We've never worked out what to do with it. Ideas and feedback welcome. It's pretty nice to use, albeit you need to use IntelliJ to edit scripts if you want IDE features.

Aerbil313

Check out nushell, the Rust of the shells.

I know it’s still not 1.0. Then just get fish. At least would save some keystrokes.

HKH2

Fish is an easy alternative. Thanks.

fbdab103

Now you have me curious - where do you see the need for floats in shell? You are more creative than myself, because I am struggling to come up with a situation where I would lean on this. A "path" type would be by far more useful to my work.

HKH2

I have a script which uses wmctrl to tile windows when I press a shortcut key (for when I'm using an editor and want the editor window to take up most of the screen).

I guess I could avoid floats using multiplication, but I would rather keep the code as simple as possible.

jjn2009

Arguably memory managed languages are the same or are you speaking to some specific slice of the available programming languages?

bjourne

That's the approach Haskell took. Didn't work since they didn't realize that waiting for your program to respond is a pretty effing huge side effect.

trealira

It has unpredictable performance because of lazy evaluation. Other high-level functional programming languages like OCaml, Standard ML, and Scheme can be compiled and achieve fairly high performance.

raggi

Learning when to invest in type constraints and when not to is an important lesson. It’s not unique to rust, though it might express a little differently. I’ve dealt with excessively typed c++ and excessively abstracted and typed Java and they have the same class of refactoring problems. I’ve also dealt with plenty of undertyped and under documented go, where there are specific values all over the place which turn into runtime footguns - and these can be truly dire to refactor as well, you get an earlier sense of progress but you ship bugs to users most of the time. There’s no magic answer to this set of trade offs. Rust gives you tools to mostly pick your place, on this specific axis it provides an unusually broad choice.

throwawaaarrgh

"Rust screams at you all day, every day, often about things that you would have considered perfectly normal in another life."

A good C compiler does this when you turn on all the flags. I like languages/compilers that let you selectively disable the screaming and let you write bad code on purpose. Bad code that works but can be written fast is often better than perfect code that takes forever to write. Once you have a bad but working POC, you can make it less bad.

"It’s got no problem attracting new users, but it’s not resulting in dramatically improved libraries or tools. It’s resulting in one-off forks that handle specific use cases."

Age has nothing to do with that. Attracting core devs is hard, and putting lots of effort into making it attractive is necessary. On top of that, cultural conventions are set by the early adopters, and a lack of convention is often just as sinful as a bad existing convention.

Take Python for example. They took a lackadaisical approach to development and runtime environments, and as a result there's 50 competing ways to develop or run a Python program. Their most-used package repository, PyPI, has been a mess for years. Nobody builds on top of existing packages, names make as much sense as a random word generator, the ecosystem is rife with malware, you can't even search for a package on the command line, etc etc. None of that is the language's fault, it's the community and core team's fault for sitting on the sidelines rather than leading. Culture matters more than the tech it's centered around.

(I'm not trying to pick on them, I just know their problems better. C has been around for a half century and its community never really put together half the solutions more modern languages did)

Someone1234

> A good C compiler does this when you turn on all the flags. I like languages/compilers that let you selectively disable the screaming and let you write bad code on purpose. Bad code that works but can be written fast is often better than perfect code that takes forever to write. Once you have a bad but working POC, you can make it less bad.

Rust supports that. Just mark everything unsafe.

estebank

That doesn't really work. All unsafe lets you do is dereference pointers or call unsafe functions. That's not gonna speed your development up during prototyping.

You can instead wrap everything in Arc<Mutex<T>> and .clone() liberally, though.

cchance

Always find it funny that people think unsafe {} means that rust ignores everything in the block, it just enables 4-5 additional abilities that are well documented, it's still doing most of its safety checks.

trealira

It will work if you only use raw pointers everywhere, like it's C. Don't use slices or strings; just pass a pointer to the first element of an array, and either pass its length separately or provide a sentinel value (like C's null terminated strings). Navigate balanced search trees using aliasing, raw, mutable pointers. Etc. This person compares the translation of C to Rust, literally versus idiomatically: https://cliffle.com/p/dangerust/

There will probably be weird behavior, though, because Rust optimizes based on assumptions about boxes and references. For example, if you have 3 raw pointers to some object live at once, and you give some library function a mutable reference made from one of those pointers, the compiler will optimize assuming it has exclusive access to that object, and it may make incorrect assumptions.

Buttons840

> All unsafe lets you do is dereference pointers or call unsafe functions.

And all those let you do is drop lifetimes and wave goodbye to the borrow checker. You just have to be explicit about it.

(What I mean is, the borrow checker checks borrows, it checks references. In unsafe you can make a reference forget what it's referring to. Just change &'a T to &'static T, and then the borrow checker is doing nothing.)

stouset

That doesn’t turn off the overwhelming majority of Rust’s soundness checks. `unsafe` isn’t a magic “let anything fly” switch.

sidlls

"Just mark everything unsafe" seems to be the motto of many (most?) crate developers. There's so much "unsafe" in dependencies used by so many Rust programs. It's a timebomb waiting to go off.

estebank

80% of crates have no unsafe code in them.

cchance

Ya i'm sure "no improved libraries" is why linux and even windows have started porting components over to rust.

ajhurliman

At the risk of getting downvoted into oblivion, that sounds a lot like TypeScript (compiler yelling at you to fix things but you just want to try an idea without perfecting it).

q8840

I'm glad to see there's a wide range of opinions here.

I'm not too concerned with performance or safety (since my code hasn't been seen very often).

I use rust just because it has a lot of "libraries that help me develop".

While other language libraries have a poor search experience and ranking system, Rust has a great library search system.

I use it because I don't have to read blog posts like C/C++ or java to find libraries that help me develop, or wade through unnecessary libraries like C# or go, I can use the rankings, and it has a good documentation system.

nlnn

This has mostly been my experience too. I like using Rust because it reduces the cognitive load I have to deal with for many things (performance/safety are nice, but not a priority).

As you've said, finding great libraries is easy, and so is adding/building them. I find this a nice change from e.g. Python (where there are so many way of different competing ways dealing with packaging/dependencies).

Also thanks to the error handling, sum types and the like, I don't have to worry so much that I've deal with all errors, handled all enum variants, etc., the compiler takes care of that.

Animats

Some days I have misgivings, but not about the language. About the crate situation. Too many important low-level crates stuck at 0.x. I've previously written about problems with the high-performance 3D graphics libraries, but only game devs care about those.

At the language level, the big problem is back references. Sometimes you do need them, and the only safe way to do them at present involves reference counts in the forward direction and weak references in the back direction. Then you have to call .borrow() and .upgrade() too much. I'd love to see a static analysis solution to that.

(Rough outline of such a solution: Owning object belongs to Owner trait. Owned object belongs to Owned trait. Owned object has .owner() and .owner_mut() functions which retrieve references to the owner. Owners probably have to be pinned, so they can't move while a back-reference exists. If an Owner changes a reference to an Owned object, the back reference is automatically updated.

That's the easy part. Now figure out how to prove by static analysis that a specific use of this does not violate Rust's no-aliasing rules (N read-only, or 1 mutable). This looks do-able for the non-mutable case, because having a non-mutable reference to both owned and owner is OK. Mutability, though, is tough. Anybody thinking about this?)

cratermoon

> Too many important low-level crates stuck at 0.x.

Is it fair to say that Rust failed to supply a robust set of standard libraries comparable to other modern languages? Or was the language aimed at level geared towards implementing rather than providing libraries? If it's truly a systems language, then what library features are essential, and what are 'nice to have'?

carlmr

IMHO a lot of these 0.x libraries would be called 3.. In other languages S.

They often have better quality than what I find in "mature" packages in npm or PyPi.

Rust kind of has a perfectionist touch to it which makes it really hard to say "this public API is stable". So people stay at 0.. way longer than normal.

I'm fine with that.

The lack of namespacing and heavy namesquatting is rather what's annoying.

nindalf

There was a conscious decision to avoid providing a vast standard library because those inevitably become outdated with time. Building a standard library is relatively easy, maintaining for decades is hard.

But it’s a trade off because there are concrete upsides to a large standard library. I wrote about this more - Rust has a small standard library (and that’s ok) - https://blog.nindalf.com/posts/rust-stdlib/

cratermoon

That does seem to align with my perception of where Rust wants to be: a low-level systems language. For example, the stdlib provide a time package that deals with duration and instant, sufficient to interact with file systems and such, but leaves dealing with dates, times, and all that complexity to the chrono crate.

What I would encourage the Rust community to focus on is adding security-sensitive functionality to the standard library. Not the big wad of complexity that is cryptography, but a standard PRNG/RNG/CSPRNG would fit.

Also, I do think it's a bit of a mistake to not at least define a relational database API. As it is we have sqlite, mysql, and postgres with similar but slightly different APIs.

Asraelite

> Now figure out how to prove by static analysis that a specific use of this does not violate Rust's no-aliasing rules (N read-only, or 1 mutable).

Out of curiosity, is there any existing language (including research languages) that can do this already?

Animats

Not that I know of. But few other languages have a borrow checker.

You can do this in Rust at run time, with Rc, Weak, and .upgrade().unwrap(). If none of the .unwrap() calls fails, you did it right. If you can prove the unwrap calls can't fail, you don't need the run time checking. So the goal is to check at compile time something you can now check at run time at higher cost. That's what Rust is all about.

Daily Digest email

Get the top HN stories in your inbox every day.