back

by rvz·7y ago·view on hn ↗
A brand new shell, especially in Rust is a great thing to see and also how it tackles the problems that exist in other shells.

However, its interesting to see so many dependencies that are required to build it (73 crates last time I checked) and as shells have always been portable across many operating systems, I wonder how portable nushell would be since it wishes to replace my current shell.

6 comments
I think the JavaScript/npm world has trained a generation of devs that high dependency counts are a code smell. This can be true, of course, but that's largely because of external factors.

In a perfect world, a language's ecosystem would have a tiny stdlib and allow importing of libraries for everything else. The Linux philosophy would be followed strictly.

The problem is just that the overhead in securing, maintaining, and organizing all those libraries is pretty large, as we've seen by npm's repeated failures. Of course the *nix community seems to have largely solved the problem, but there's also a massive culture of volunteerism benefiting them.

I think there’s definitely room for mirrors of package repos that focus on a stablized and audited subset of packages, with frequent security and bug backports and medium-length cycles for new features — like what Fedora and Debian do, but for language ecosystems.
Agreed. ActiveState has had that as a commercial offering for quite some time (https://www.activestate.com/products/) and Red Hat is getting into it with (OpenShift) Application Runtimes. Given that providing such a thing is hard not-so-fun work I have a hard time seeing it getting available for free. (ASP).NET Core might be alone in being well-financed and free, something I think will lure (enterprise software) developers over long-term.
I believe Deno is doing this.

For a FOSS language, a large stdlib can be similar to a curated repo in practice.

UNIX philosophy. You mean the UNIX philosophy.
>…the Linux philosophy is "laugh in the face of danger". Oops. Wrong one. "Do it yourself". That's it.

- torvalds

I did. Thank you for the correction. Linux did, for most of its history, do an admirable job of upholding that philosophy though.
Care to elaborate? Linux is a huge monolithic kernel, isn't this the exact opposite of the UNIX philosophy?
The ecosystem, not the kernel. Linux packages are relatively good about doing one small thing well.
It is safe to say that it is unrealistic to expect that developers in the real world audit their dependencies.

I'd imagine a feasible solution being a permission-based system (which might already exist), where programs and their dependencies do not have access to certain facilities, like the filesystem, other processes or the network, without them being declared in a manifest file of sorts. Permissions should be inheritable when explicitly specified, and a sub-dependency could not gain a permission it didn't inherit from its parent. Unfortunately this does not work so well with a shell, since the shell needs the ability to spawn arbitrary processes. At least the shell itself would not have network access, forcing it to rely on tools already installed in the system

Also, if we resort to some magical wishful thinking, then all the tools in the system follow this permission model and the package manager is aware of the permissions. You could then query the package manager for all installed software with a certain capability, like network access, disabling tools like curl and netcat.

The key difference between typical GNU/Linux distributions and the npm/cargo model is that the set of packages available in the distributions is to some extent curated.
I hope that I implied that point when discussing the labor required for a highly modular ecosystem to function well.
There are 464 dependencies in the complete tree when doing `cargo install`.

That's a bit too many. But it's not often you compile your shell from scratch.

It's definite a lot. We're looking into making more of the features optional, so you can streamline it if you want.
That is some impressive insight to the behavior of the lib. Have you ran into that before and knew it was an easy thing to get rid of?

I’m always impressed when people show up with fixes like this (that I wouldn’t even know to look for).

I did see quite a few that double up, like 2 versions of url and a few others.

Just had a quick play ... on arch linux `yay nushell-git` already works!!!

One area where I got a bit stuck was around help. `man where` gave me nothing, `help where` also. I tries stuff like `ls | where type = File` and got a type error. I think it would be amazing if this thing onboarded people a bit nicer, "where needs a condition, to learn about where type "help where" ... stuff like that

Overall really enjoying the ideas here and I am absolutely going to be following this!

And 484 when installing with rawkey and clipboard.
> shells have always been portable across many operating systems

Most shells work on just one of either Unix or Windows; the blog post specifically mentions Windows support as being something on the radar of the developers, which Rust is arguably a better fit for than C/C++ (which most shells are written in) due there being a single compiler, standard library, and build tool with first-class support for both Unix and Windows in the Rust toolchain.

Which shells are those? Bash works fine on both Unix and Windows.
Mario gives a lot of the benefits of using objects instead of text, but since it doesn't control the whole shell it's less of a commitment.

https://github.com/python-mario/mario

The risk of a supply chain attack, roughly speaking, is then multiplied by all 73 dependencies. If any of those are compromised, the shell and system are compromised.

To ameliorate the risk, the security of all 73 dependencies would need to be at least an order of magnitude greater, just to catch up to shells with no dependencies.

The irony is that this lack of "dependency-safety" is far more easily exploitable than any previous lack of "memory-safety".

Of course, this can be easily fixed if developers stop multiplying third-party dependencies, so that importing a dependency involves O(1) risk, not O(?).

> But this can be easily fixed. Developers need to stop using third-party dependencies, so that importing a dependency should involve O(1) risk, not O(N^2).

How is this an easy fix? Now developers need to develop and maintain O(N) instead of O(1) software projects.

I guess that if nushell had a zero dependency policy, it would have never happened.

And there is a significant risk to spreading your resources thin when reimplementing sensitive dependencies, like crypto or network / http libraries.

> How is this an easy fix? Now developers need to develop and maintain O(N) instead of O(1) software projects.

At some point, developers need to take responsibility for the security of their projects. If your project is larger, you will need to maintain more code, whether directly or indirectly. You can't simply import a dependency but never audit it. And if you're taking the trouble to audit a third-party dependency, at some point it becomes worth maintaining directly.

> sensitive dependencies, like crypto or network / http libraries.

I am not suggesting that developers reimplement TCP.

I've had to reinvent the wheel, over and over again, throughout my career. Sometimes it was because our environment was constrained (no cloud, needs to be up for major disasters), or because the architecture was unique (no primary, all protocols must be decentralized), or because what I needed didn't exist for my stack.

It's pain and sorrow all the way down and I find more bugs in my reinvented wheels than in the actu code I set out to write.

How about this:

0. Rebuild from latest stable dependencies, or latest updates to your LTSB, whenever you or your dependencies have a bug.

1. Prefer static linking or lib incorporation if feasible, or use a package manager that properly guarantees you the right versions.

2. Stick to popular repos with responsive maintainers, or

3. Fork the dependency yourself, audit the code and know how to debug it.

4. Watch upstream for security bugs.

Sure, and 5 would be:

Prefer dependencies with smaller flatter dependency trees.

> At some point, developers need to take responsibility for the security of their projects.

Agreed, but that could be through careful selection instead of rolling your own. And it would be great if there was better tooling for that.

Imagine an ecosystem where every module author automatically gets not just a public repo and issue tracker, but also a way to deal with security issues, an indication if all contributors used 2FA for code contribution and package uploads. Then there could be a search for packages where you can say "only give me those with 2FA and a defined security disclosure process, including all transitive dependencies".

That wouldn't be perfect, but much, much better than any ecosystem I've seen so far.

This is sorta why things like Guava and Boost exist - audited / trustworthy libraries for common asks where you know more or less what to expect in terms of quality.

The approach the Rust community is taking with a sort of decoupled-STL approach is interesting, but I do wish they found a way to collect the known-good-bits in a common namespace.

What shells, if any, have no dependencies?
An example: https://www.archlinux.org/packages/core/x86_64/dash/

Or look at bash:

https://www.archlinux.org/packages/core/x86_64/bash/

I would say many shells that are written in C will have an extremely short list of dependencies in contrast to Rust.

On my phone, but ldd bash? Also compare cost of maintaining your own custom secure functions Vs using well known ones.
You do not have to maintain your own, you can use a popular and audited one that many other projects depend on so it is typically installed. The point is not to not have any dependencies, just do not have in the hundreds, especially not if they are not shared; it would take up too much space!

As per your request:

        $ ldd /usr/bin/bash

        linux-vdso.so.1 (0x00007ffce8b8c000)
        libreadline.so.8 => /usr/lib/libreadline.so.8 (0x00007fd76a0dd000)
        libdl.so.2 => /usr/lib/libdl.so.2 (0x00007fd76a0d8000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007fd769f15000)
        libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007fd769ea6000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fd76a266000)

Those files are generally installed on all major Linux distributions.

For completeness:

        $ ldd /usr/bin/dash

        linux-vdso.so.1 (0x00007ffdbadce000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007f6f2cd28000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f6f2cf58000)
Note that these files are shared between projects, you do not have to download and compile them per project. Can you do the same using Cargo? According to my experience with it from two years ago, I had to download and build over 400 dependencies for all projects, separately. I do not know how many dependencies Rust projects have on average.
This isn't a problem if versions are pinned.
> However, its interesting to see so many dependencies that are required to build it (73 crates last time I checked)

The problem is that Rust has two issues:

1) In Rust, you don't pay for what you don't use. This means that a lot of stuff tends to be an external library since not using something means you can exclude it COMPLETELY. The issue is that things tend to get atomized more finely than most languages would do.

2) Rust is trying to not bake things into a standard library too early. This is a good comment about standard libraries from Python:

"The standard libraries constitute one of Python’s great features, but they tend to suck up all the oxygen. Programmers are reluctant to write libraries that duplicate their functions, so poor libraries in the standard set persist. Only a few people, like Reitz, are willing to write modules that compete with the standards."

https://leancrew.com/all-this/2012/04/where-modules-go-to-di...