Hacker News new | past | comments | ask | show | jobs | submit login
Fish 4.0: The Fish of Theseus (fishshell.com)
718 points by jdxcode 19 hours ago | hide | past | favorite | 130 comments





Congrats to the fish team! Great writeup with lots of interesting detail.

I wonder if this is the biggest project that has moved from C++ entirely to Rust (or maybe even C to Rust?) It probably has useful lessons for other projects.

If I'm reading this right, it looks like fish was not released as a hybrid C++ / Rust program, with the autocxx-generated bindings. There was a release during that time, but it says "fish 3.7 remains a C++ program" [1]

It sounds like they could have released if they wanted to, but there was a last stage of testing that didn't happen until the end.

Some people didn't quite get the motivation for adding C++ features to Rust [2], and vice versa, to enable inter-op. But perhaps this is a good case study.

It would be nice if you could just write new Rust code in a C++ codebase, without writing/generating bindings, and then throwing them away, which is mentioned in this post.

---

Also the #1 gripe with Rust seems to be that it supports version detection, not feature detection.

But feature detection is better for distros, web browsers, and compilers:

Feature Detection Is Better than Version Detection - https://github.com/oils-for-unix/oils/wiki/Feature-Detection...

Version/name detection is why Chrome and IE pretend to be Mozilla, and why Clang pretends to be GCC. Feature detection (e.g. ./configure and eval() ) doesn't cause this problem!

[1] https://github.com/fish-shell/fish-shell/releases

[2] e.g. https://news.ycombinator.com/from?site=safecpp.org


To clarify, work on the rust rewrite started after 3.7.0, but the C++ code remained in a working branch on the git repo. Midway through the rewrite, we backported additions and improvements to fish scripts (most observable being new and improved completions) and a couple of important bugfixes from the rust-containing `master` branch to the C++ branch and released that as 3.7.1.

We never considered releasing anything with a hybrid codebase; aside from the philosophical purity of fully making the switch to rust, it would have been a complete distribution nightmare (we take package maintainer requirements very seriously). Moreover, the code itself was not in a very pretty state - the port was very much like trying to undo a knot: you had to make it much uglier in order to get it properly undone. There were proverbial tons of SLoC that were introduced only for transitional interop purposes that were later removed, this code was never held to the same quality standards (in terms of maintainability; it was still intended to be bug-free and required to pass all our unit and integration tests, however).

As mentioned in the article, we prefer to do feature detection when and where needed/possible. The old codebase was purely feature-detected via the CMake build system but we ended up writing our own feature detection crate for rust invoked via build.rs (maintained here [0]) though we just defer to libc on a lot (which doesn't do that yet). One side effect of the libc issue is that we're beholden to their minimum supported targets (though I'm not sure if that's strictly the case if we don't use the specific apis that cause that restriction?), which are higher than what we would have liked because we were fine with feature detecting and implementing using both older and newer apis where needed.

[0]: https://github.com/mqudsi/rsconf


> Feature Detection Is Better than Version Detection

The problem with feature detection (normally referred to as configuration probing), at least the way it's done in ./configure and similar, is that it relies on compiling and potentially linking (and sometimes even running, which doesn't work when cross-compiling) of a test program and then assuming that if compilation/linking fails, then the feature is not available.

But the compilation/linking can fail for a myriad of other reasons: misconfigured toolchain, bug in test, etc. For example, there were a bunch of recent threads on this website where both GCC and Clang stopped accepting certain invalid C constructs which in turn broke a bunch of ./configure tests. And "broke" doesn't mean you get an error, it means your build now thinks the latest Fedora and Ubuntu all of a sudden don't have strlen().


IMHO a broken toolchain is a broken toolchain and that's kind of outside the scope of autoconf -- and I say this despite having banged my head against the wall only too many times as a result of an odd toolchain misconfiguration leading me into chasing autoconf gremlins.

One thing about rust is that it has always treated cross-compiling as a first-class citizen. Cargo is very intentional about the difference between the HOST and TARGET triplets and you can't mix them up unless you are doing so intentionally.

The rsconf feature detection crate was similarly designed with cross-compilation in mind from the start and eschews running binaries in favor of some clever hacks to exfiltrate values during the cross-compilation process.

There is only one rsconf feature (retrieving compile-time constants) that is currently labeled caveat emptor as it does not support cross-compilation; perhaps I can nerdsnipe someone here into figuring out a workaround: https://github.com/mqudsi/rsconf/issues/3


Hm what's an example of those invalid C constructs? I'd be interested in seeing what happened

One answer is the __has_feature tests mentioned in a sibling comment. Then you are using a supported API, not arbitrary code. Browsers should probably support something like that, if they don't already.

But the arbitrary code is still a useful fallback, for when the platform itself doesn't support config probing

I think you're saying that "writing good ./configure is hard", which is absolutely true. But it's still true that feature detection is better than version detection.


The XZ utils supply chain attack also used this to sneakily disable Linux Landlock: https://news.ycombinator.com/item?id=39874404

Although Clang does set the `__GNUC__` macro and you have to distinguish it using the `__clang__` macro, Clang and GCC also both have very fine-grained feature detection features as well, both at the CLI level and in the preprocessor (such as the `__has_feature` family of builtins).

I remember switching from bash to zsh a few years back and thinking I was the bees knees. After the switch trying other shells seemed like bike-shedding because, I mean, what more could a shell? Then I got a new computer and decided to start from scratch with my tooling and downloaded fish. I was shocked how it instantly made zsh feel cumbersome and ancient.

Heartily recommend others give it a try as a daily driver for a couple of weeks. I liken it to Sublime Text: an excellent “out of the box” tool. Just the right amount of features, with the option to add more if you want. But you also don’t feel like your missing out if you keep it bare bones. A great tool in and of itself.


Interesting, I went the other way about 7 years ago - switched from fish to zsh (initially with oh-my-zsh). The interactive experience was similar enough on both shells, and the performance was great on fish and okay-ish on zsh, but two things won me over:

1. With zsh, I can copy-paste some bash snippet and in 99% of cases it will just work. Aside of copy-pasting from StackExchange, I also know a lot of bash syntax by heart by now, and can write some clever one-liners. With zsh, I didn't need to learn everything from scratch. (I guess this matters less now that you can ask AI to convert a bash one-liner into fish one-liner?)

2. For standalone scripts... well, I think it's best to reach for a proper programming language (e.g. Python) instead of any shell language, but if I had to use one, I would pick bash. Sure, it has many footguns, but I know them pretty well. And fish language is also not ideal - e.g. IIRC it doesn't have an equivalent of `set -e`, you have to add `; or return 1` to each line.


I use fish and on the very, very rare occasion I need to copy and paste bash from the internet it's pretty easy to just type 'bash' into fish and paste it in. Its not like bash and fish conflict, you can have them both installed.

FWIW, fish is much more bash-compatible these days. We've introduced support for a lot of bash-isms that don't completely break the fish spirit or clash with its syntax in the last few releases.

I personally liked "; and" but... "&&" solves around half of the problems with copy-pasting and does not look terrible, so it was probably the right thing to add.

I “devolved” mostly along the same path. Bespoke shell to OMZSH to Zsh to Bash.

Zsh has a few nasty Bashism footgun incompatibilities. If I remember correctly the worst one is with how globbing / “*” works, which is why that is guarded with an option.

My main reason for sticking with Bash is that it’s everywhere, and the places where it isn’t try very hard to support the most-used featureset of Bash.

A stock Bash shell does feel a little naked without my dotfiles though :)


Reading the associated issue (https://github.com/fish-shell/fish-shell/issues/510) about the lack of "set -e" was interesting as it highlighted how weird Bash, and shell scripting in general, is from a programming language perspective. Imagine programming in any other environment where every function you call could either succeed or fail catastrophically. There's some talk about adding exception handling to Fish, but maybe the sensible thing to do is to have a mode where Fish ensures that you've dealt with each possible error before moving on. Which is what you would do anyway if you were invoking external programs from a non-shell language (like Python's subprocess.check_call).

In any case the discussion in that issue made a convincing (to me) argument that if you're doing the sort of scripting for which "set -e" makes sense, which is most of it, you should be using Bash. That doesn't mean you need to use Bash interactively though, as others have pointed out.


> Imagine programming in any other environment where every function you call could either succeed or fail catastrophically

There's not much to imagine since that's pretty much every other language?

Sure you can recover with error handlers (sometimes[0]), but by default all of them will hard abort in case of exceptions.

In our modern language landscape shells are very much the odd ones, where errors are completely silent by default and the thing just carries on oblivious that the world around it might be crumbling completely.

[0]: https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-...


> Imagine programming in any other environment where every function you call could either succeed or fail catastrophically

Laughs in client-side JS.


Hmm? It’s not like a JavaScript exception crashes the entire browser tab.

Client-side JS is event-driven. An unhandled exception stops processing for that event, but doesn’t block other events.


But, scripting languages are not programming languages, scripting languages are made to run commands, and by default a script should halt if a command fails, at least in the CLI execution context. The problem is, scripting languages mix programming context and scripting context, so a condition written in the script shouldn't be treated as a CLI exit status. Anyway, I don't use fish for scripts just for the lack of exit on command error. That's essential while scripting.

> 2. For standalone scripts... well, I think it's best to reach for a proper programming language (e.g. Python) instead of any shell language, but if I had to use one, I would pick bash. Sure, it has many footguns, but I know them pretty well. And fish language is also not ideal - e.g. IIRC it doesn't have an equivalent of `set -e`, you have to add `; or return 1` to each line.

I'm sure you know this, but: no particular reason the interactive shell you use has to match the shell you use for scripts. All of my scripts are in bash, but I haven't used bash interactively in decades now, at least on purpose.


I write all my scripts with the hash bang as "#! /bin/bash" so even though fish is my interactive shell, I still use bash for all shell scripts. I think the restrictions you mention only apply if you use "#! /bin/sh" rather than bash specifically.

Just fyi, you should use `#!/usr/bin/env bash` instead of `#!/bin/bash` or whatever because you can't assume the location of bash (but the location of `env` is indeed portably fixed). e.g. FreeBSD (and macOS?) has bash at `/usr/local/bin/bash`

That assumes you care about portability. Not everybody does.

Writing portable software is difficult, and doing it for shell scripts even more so. Blindly pursuing portability for its own sake is not worth it. Weigh the cost of portability against the odds that the software will ever run on different systems.

For me personally it is never worth it to write my personal programs portably. This would require that I test them on different systems that I do not even use. Pointless.


And NixOS has bash somewhere in the Nix store... :)

Clarification: /usr/bin/env should be used for pretty much every shebang since it looks up the binary on $PATH.


bash is /bin/bash on macOS, unless the user really likes bash, in which case it's probably /opt/homebrew/bin/bash or /opt/local/bin/bash

I'm confirming. Often, when you run a script on more than just your own computer, bash is located in unexpected places.

For me, for example: `/data/data/com.termux/files/usr/bin/bash`

In such cases, scripts containing the absolute path to bash in shebang do not run correctly.


I think that oilshell is aimed at people like you. I’ve never used it, but their website does make some interesting points about how a shell ought to work and how this could be compatible with bash.

As a go programmer, "; or return" makes a lot of sense to me

Same here. I used it for about 3 days before I installed it on all my systems and permanently switched. For me, it was like the first time I learned a non-Latin language, and my eyes were opened to how much stuff I took for granted was completely arbitrary.

For example, here's how you write an autoloaded function "foo" in Fish: you make a file called "foo.fish" in its config directory. Inside that, you write "function foo ..." to implement it. There's no step 3. That's it.

Want to customize your shell prompt? Follow the process above to write a function called "fish_prompt" that uses normal scripting things like echo, pwd, git, or whatever to write your prompt to the screen. There's no step 2. That's it.

Fish was revelatory. Other shells of the same vintage feel hopelessly outdated to me now. For context, I was the maintainer of FreeBSD's "bash-completion" port for a few years way back when. It's not that I don't have experience with other shells. I have plenty. I just don't want to use any of the others now.


This was more convincing to me than the GP comment, especially the shell prompt part.

Is the “foo.fish” name required? Could I have “bar.fish” with “function foo…” inside and still autoload function foo?

Not autoload, no. You can have as many functions as you want in a single .fish file, but it'll only be lazily autoloaded if it has the same name as the command you are trying to execute. It's how we avoid doing the I/O of scanning all fish directories and parsing their contents at startup.

...and you can still explicitly source the files if you want to load the functions elsewhere.

I went bash -> fish -> zsh.

The main reason I switched is because zsh can (often) source bash scripts and can use bash completion scripts (usually), and I was tired of having to translate things from bash to fish. I also ran into a few things where something that was relatively easy to do in bash was impossible to do with fish. But that was years ago so maybe that is less of an issue now, and I don't remember exactly what it was.

Having used zsh, I think a big advantage it has over fish is the completions. There are completions available for more programs for zsh, and the zsh completions are sometimes higher quality in zsh.

But I do generally like the syntax, and good out of the box experience of fish. I wish it had a bash or even posix compatibility mode and more available completions.


I can relate with your comment a few years ago, but later the situation drastically got better, while not perfect yet (i.e. I still need a custom autocomplete function for aws). You might want to give it a try now anyway.

My only issue with Fish is when pasting things from the web that assume Bash, a lot of the time it just works, then now and then I get screwed. I don't know nearly enough Fish or Bash to switch. Still though, I prefer Fish ultimately.

I know it's a typo but this:

> what more could a shell?

Is quite good. It could almost be the tag line for fish shell.


Do you mind sharing what you think are the killer features of fish?

Fish has a lot of features out of the box I find really useful:

* Command auto suggestions as you type based on your history

* History search (using up arrow) based on a partial command

* Helpful completions and descriptions when you hit TAB

* Muti-line command editing

* Syntax highlighting

You can get all those same features in Zsh by using plugins, but those features work out-of-the-box with Fish with zero configuration. Zsh is a bit of a pain to configure, and pretty anemic without plugins. Fish makes configuration optional because it works how you'd hope your shell would out of the box. Even though Zsh has those features as plugins, they're kinda janky, not well maintained, and often conflict with other plugins.

Additionally, Fish also has:

* Excellent built in commands (string, math, argparse)

* Sane scripting (word parsing where you don't need to quote everything, etc)

* Great documentation

* A web-based configuration if you're into that sort of thing (it's a bit of a gimmick for beginners)

The main reason I use Zsh (or Bash) at all is for POSIX/portability, or for when I can't install something else. But for an interactive shell on a machine I control, it's hard to compete with Fish for speed, features, and ease of use.


For me, it's that the ergonomics are straightforward, and everything works out of the box. If I find myself on a new machine, just installing fish gives me an ergonomic setup without having to install too many additional tools or mess with configuration.

Also, fish_config is there if you want to make quick changes without having to look up syntax.

We had the exact same experience, still in love with fish!

> The one platform we care about a bit that it does not currently seem to have enough support for is Cygwin, which is sad, but we have to make a cut somewhere.

> We’re also losing Cygwin as a supported platform for the time being, because there is no Rust target for Cygwin and so no way to build binaries targeting it. We hope that this situation changes in future, but we had also hoped it would improve during the almost two years of the port. For now, the only way to run fish on Windows is to use WSL.

I understand, but this is indeed incredibly sad. To this day I still use Cygwin, and in fact prefer it to WSL depending on what I'm doing. Cygwin is an incredible project that is borderline miraculous for what it accomplished and provides. Without Cygwin I may not have any sanity left. I can't exude enough love for the Cygwin team.

Hopefully rust will support cygwin as a build target in the future!


There's nothing technically stopping Rust from supporting Cygwin, except lack of volunteers to complete the port:

https://github.com/rust-lang/rust/issues/5526

(this feature request has been open for 12 years)


genuinely curious: with so much love for cygwin, why not just run Linux? possibly with a dual boot?

Corporate jobs are nearly always on Windows machines. Cygwin+GitBash can usually sneak past the gate without raising too many eyebrows. WSL is still voodoo dark mark that can require conversations to get IT to allow.

Exactly. I have been running Linux on my personal computer for 15 years now. But frequently for work purposes and corporate jobs I have to work on Windows.

Switching between OSes is a hassle. Besides WSL already has all the loveable parts of Linux, there isn't much point in dual booting anymore

If you’re using WSL, why use Cygwin?

If you're at a very large corporation, Cygwin could have been approved decades ago, but WSL is still going through "auditing". WSL is still new enough that bureuocratic organizations haven't satisfied themselves enough that it can be secured or are just plain stubborn enough to not want the hassle.

I refuse to work at large companies for this reason, but one company I worked for brought on a large American bank as a customer and their infosec terms for vendors essentially required their IT "standards" on us, which sucked as we were a Mac shop. It almost came to a head when all the developers were told they had to seek approval for upgrading their build tools.


To avoid using cmd.exe/PowerShell for stuff that needs to run natively. For example, when I used Linux I'd use this very small program named darkhttpd for sharing files among my computers over WiFi; when I switched to Windows I compiled it on Cygwin and it worked just as fine.

It's strange how the article starts off complaining about C++'s platform "issues":

> We’ve experienced some pain with C++. In short:

> tools and compiler/platform differences

before conceding that, because of Rust, they 1) are actually dropping support for a platform they previously supported and 2) can only support (in theory) a small fraction of those platforms supported by g++, but that that's OK because those are the only platforms which really matter. I get that it's a trade-off, but it would have been more intellectually honest to just admit this is one area (portability, backwards compatibility, and ABI stability) where C++ mops the floor with Rust, instead of pretending it's a another paintpoint Rust avoids.


I don't see how the article is pretending anything. They had platform issues with C++ (portability and usability on the platforms they supported), and switching to Rust fixed those issues but gave them a different set of platform issues (they could no longer support Cygwin).

Neither c++ nor rust is a clear winner in portability and platform support. C++ is available on more platforms, but in some ways rust makes it easier to support multiple platforms than it is in c++, for example using rustup to install the latest version of the compiler.

What they got from this isn't that they can now support more platforms, but that they now don't have to spend as much effort on supporting dealing with differences between different platforms.


> Neither c++ nor rust is a clear winner in portability

C++ is the clear winner in portability because of GCC and the wealth of platforms it supports. You can argue you don't care about supporting, e.g., OpenBSD on PPC, but trying to hand-wave away this advantage C++ has over Rust is disingenuous.


C++ is the clear winner in what portability is possible.

But as they outlined, in a lot of cases achieving it is substantially more effort per arch+OS+version target than Rust.

Getting a better ROI on your time is a valid reason to consider something better for your use cases.


It's not disingenuous, you just missed this part:

> but in some ways rust makes it easier to support multiple platforms than it is in c++

The ease of installing Rust on Windows has helped build a culture of cross-platform libraries/crates, and so it's significantly easier to build applications that support more platforms than C++.

Take a library for coloring text on a console, and chances are, it'll work on cmd.exe too (despite that not even using ANSI escape codes).

Take a library for loading other libraries, and it will load .so, .dll, and .dylib too, with feature-gated methods to deal with each platform's quirks.

Rust's standard library helps a bit, e.g. it isolates platform-specific modules into things like std::os::unix and std::os::windows, so it's a bit more obvious when you're trying to use something that won't work on all platforms.

However, it's more just a cultural thing; many Rust things work cross-platform now, people see how nice that is, and so people try to maintain the status quo.

With C++, you often need MSYS or Cygwin, and those have their own limitations; you can certainly make something in C++ that won't need them, it's just harder in comparison.


C++ may run in more places than Rust but that's nothing to do with how good the tooling is. C++ runs everywhere AND its tooling is abysmal.

Yeah, it's somewhat interesting that they point to Debian's popcon (which is opt-in), when the statistics are basically coming from amd64, whereas I think it would be much more interesting (if possible) to see what the number of installs of fish are on openwrt (and other embedded distros). Currently the openwrt fish install is ~2MB (which is massive on a router), I wonder what the new install size will be with the rust version, and if practically they've dropped everything except desktop/server linux and MacOS?

Rust binaries, once stripped, are not necessarily much fatter than their C++ counterparts. We're not expecting a huge increase there.

Embedded distros should still be supported, though you might need to cross-compile for a few depending on rust toolchain availability. Cygwin is supposed to be getting a working rust target at some point, but who knows?


I fully expect fish 4.0 to be in openwrt (because openwrt runs on x86_64), but what percentage openwrt machines have the space to run it would be interesting (I suspect it won't be an issue, as likely those near the lower limit of openwrt are probably sticking with busybox anyway, and not installing a different shell).

Personally, what I want is inexpensive hardware (routers, but also storage devices) that don't use much power (e.g. ~5W) but are also viable targets for projects like fish, so we can all have nice things.


I know rust-fish is being used on at least some non-x86_64 non-aarch64 routers because we've accepted patches by those users to make it build on platforms without native 64-bit CAS.

That would be any pre-ARMv8.1 machine?

> The one goal of the port we did not succeed in was removing CMake.

> That’s because, while cargo is great at building things, it is very simplistic at installing them. Cargo wants everything in a few neat binaries, and that isn’t our use case. Fish has about 1200 .fish scripts (961 completions, 217 associated functions), as well as about 130 pages of documentation (as html and man pages), and the web-config tool and the man page generator (both written in python).

Our issue for this is https://github.com/rust-lang/cargo/issues/2729

Personally, I lean away from Cargo expanding into these use cases and prefer another tool being implemented on top. I've written more about this at https://epage.github.io/blog/2023/08/are-we-gui-build-yet/


(hi Ed!)

I would definitely love to see Cargo have the ability to do this -- it means that `cargo install --locked` stays as a viable approach. It probably won't apply to fish, but I think being able to run a post-install command from the binary you just installed would suffice for my needs.


We've actually added support to make single-binary fish deployments possible by (optionally) bundling static resources that would be part of the CMake-based deployment into the binary itself and having it unwrap those on first execution. The limitations of Cargo and the idiomatic `cargo install` usage primarily motivated this.

I'm a big fan of this solution! It's always been annoying to perform all the ceremony involved in deploying a system with a bunch of files, with config, scripts and system written in a bunch of different languages.

In my current project I just wrote the installer and config generation as part of the main method. Gets rid of a lot of complexity, with a simpler build, and is arguably easier to maintain. Single language, single binary.


Ah interesting, I looked through your build.rs for the "installable" feature, and it looked like you were running sphinx-build in there. Do you plan to ship those artifacts in the .crate file?

Surprised to see the line count go up so much, 56K LOC of C++ to 75K of Rust. The blog attributes it to rustfmt using less oneliners. Even so, i would believe that should be a small factor compared to the heaps of duplicate code you get from c++ header files and all the other syntax ergonomics rust gives you.

Is this typical for such a translation. They also mention addition of new features contributing to more code, how much of the addition was new features vs pure translation?

Would be interesting to see the line count of the c++ version if it was run through a formater with similar configuration.


Rust is denser than C, but both Rust and C++ can work on similarly high level of abstraction.

It may be just down to rustfmt. It really adds a lot of vertical sprawl. I personally can't stand how much rustfmt makes multi-line code explode.


The tone in the "The Timeline" section seems apologetic:

> The initial PR had a timeline of “handwaving, half a year”. It was clear to all of us that it might very well be entirely off, and we’re not disappointed that it was.

I'm amazed that you estimated it at so little time originally, and I'm amazed you shipped it in full in just 2 years. Congrats!


Absolutely. Staying within an order of magnitude for a project of this size is just really good eyeballing. :)

I try not to post unsubstantive comments here but I’m just so moved by this success that I have to say an enormous Congratulations!

Thought for a second that this was a 4.0 release announcement but this is just about the rewrite in rust. Any fish users wanting release notes of what to look forward to can look here: https://fishshell.com/docs/4.0b1/relnotes.html. Glad the rewrite is helping the dev team make improvements, but I’m more excited for the actual new features (except the new alt-backspace behavior which I’m sure I’ll get used to).

Very nice too see Rust being used where it is actually appropriate! Hopefully Rust "easy" multi-threading will allow more parts of fish to be async, even though it's already much better in that regard than bash (or any other shell I've seen).

One weird thing I'd also like to see is more bash integration, as others pointed out that being their primary motivation against switching to fish full-time. My use case is mostly sourcing bashrc/bashevv, and theoretically it should be possible in fish if I understand correctly: you need to be able to import e.g. every new env variable that changed before and after sourcing a bash script via real bash.


> it is often better to use if cfg!(...) instead of #[cfg(...)] because code behind the latter is eliminated very early

My experience with this is the other way around, especially if you have crates tied to that feature.

The cfg! is a marco that compiles to true/false, so whatever is inside of the if guard needs to compile regardless.

E.g.:

Cargo.toml

    [features]
    default = []
    my_feature = ["deps:feature_dependency"]

    [dependencies]
    feature_dependency = "1.0.0"
And in code:

    if cfg!(feature = "my_feature") {
        feature_dependency::something::Something::invoke();
    }
This will fail if you compile without `my_feature`.

That was the point. The paragraph is talking about how errors only show up in some configurations, leading to “works for me” behavior for some of the devs. When you can get away with cfg!, you are more confident that it will at least compile regardless of the config being checked.

I might be wrong but most optimizing compilers will treat "if false" and the following code as dead and remove it.

It will remove it, but not until after resolving symbols. If the branch-never-taken references a missing library then this will still error, which is the problem for a feature flag.

Congrats to the Fish team. The best shell just got better.

How about updating the project tagline to: "Finally, a shell for the 00s!"


Thanks but one cannot be too ambitious like that! '00s would mean the end of zip drives, dealing with unstandardized flash drives flakier than the floppy disks of old, and supporting point-and-shoot digital cameras!

That’s true. Better to stick to the 90s where we are safe.

I guess the author meant "the shell for '000s", but that's too much to type

We're flush with new and awesome terminals lately, Ghostty public launch now a huge upgrade to fish.

I've tried Fish a few times but hard to migrate over from bash/zsh. Does anyone have tips on how to port over a bunch of aliases/scripts/etc. easily?


You don't need to port your scripts. Migrating aliases shouldn't be too difficult.

I am curious to ask others here, are there other low-config alternative tools like Fish that, looking back, now seem like a no brainer? Ghostty is a recent example, Helix seems like another. I’d love to know about other tools people are using that have improved or simplified their lives.

Agree with you on helix. I love it.

Atuin for improved history search.

Starship for an improved shell prompt.

zoxide - better cd

ripgrep - better grep

just - a command runner. I put project specific commands/scripts in there so I don’t have to remember.

All of these are indispensable for me.


fd a better find is one I like.

fastmod is a better sed.

mise, uv, ruff, starship is my current list.

Really happy to see this, such a mammoth effort by the team and everyone else involved.

I switched over from zsh about four years ago and my config went from several hundred lines to a handful with just one plugin (fzf.fish).

It just works how I expect it to and I can't imagine changing again any time soon.


Awesome to see. Can't wait to see how things improve from here.

Here's the code if you were looking for it: https://github.com/fish-shell/fish-shell/tree/4.0b1


I'd be really interested to hear from distro packagers how this is going - how amenable is rust-fish to being packaged following e.g. Debian guidelines?

We took an incredible amount of care to consider the package maintainer requirements for our the most popular distributions using/distributing fish. One of our maintainers is very careful about letting us know when we're doing something that might upset distro packagers, and we're constantly letting package maintainer guidelines and requirements influence how we structure fish itself and which dependencies we pull in.

It's hopefully not too tricky - it can't be packaged as a crate using (say) debcargo, as the install path still requires CMake. The Debian experimental package changes are mostly about pulling in the right dependencies (including some internal mangling to support some policy choices).

Can I `source` my custom .bash_profile file into Fish? Trying it out it doesn't seem to work

> Fish also uses threads for its award-winning (note to editor: find an actual award) autosuggestions and syntax highlighting, and one long-term project is to add concurrency to the language.

(note to editor: find an actual award)


The two most popular zsh plugins are total clones of this at 31k and 20k gh stars respectively. Not an award but certainly an indication of its success.

Having used zsh with those plugins for a while and not having used fish personally, I'll nominate them for "most desirable plugins to copy for your own shell to make it more user-friendly".

Seriously, can someone find them an award? I think they've earned it.

Achievement unlocked: Centurion! _get over 100 comments on hacker news_

it's a joke my friend

They should make an award, like RL Stein did

https://x.com/RL_Stine/status/1337768882988347393


Or like any car award that oems advertise.

> 57K Lines of C++ to 75K Lines of Rust

...

> A lot of the increase in line count can be explained by rustfmt’s formatting, as it likes to spread code out over multiple lines, like: ...

I wonder what the character count diff is?


I love fish and I’ve been a user for years. In the wake of AI, I am really interested in getting out of completes via a local tiny AI model

Does the fish team have any plans for integrating AI models for other completion?


We ship fish with completions ollama and llm, maybe others. We don't have any plans to "integrate" AI into the actual codebase.

I had no idea! Where can I find docs to set this up?

Take some agency for yourself.

https://github.com/search?q=repo%3Afish-shell%2Ffish-shell%2...

That agency includes interpreting the search results alongside the reply to your original comment.


I use the shell a lot every day, mainly bash and some ash (alpine).

Does something like fish make the experience a bit smoother? is it pretty easy to get into?


To answer your first question specifically, yes. With fish you get substantial ergonomic improvements over bash and ash out of the box. There's also a very minimal learning curve since these features build on familiar idioms. There are some differences with fish as a language that take some getting used to, but bash is always one command away if needed for more complex stuff.

It's absolutely worth a test drive to see if the features excite you. If the lack of bash familiarity is too much of a blocker, then zsh with plugins that provide the same features as fish might be worth a look too.


I have to ssh into different places quite frequently and keeping the zsh configuration synced was a pain. Fish had 95% of what I needed out of the box so rather than putting a ton of scripts in place, installing omz, plug-ins etc, I run a single install command.

fish is intended to be beginner friendly, whether you're new to the commandline world or not. It's essentially configuration-free, so that's about as easy to get into as you might imagine.

Side note: beginner friendly or not, I've been using various kinds of shells for several decades and I still find Fish delightful. It's friendly to beginners, but also very comfortable to old salts.

Congratulations. Rust is really a great language. I wrote a small rust web server and its been a year and it seems to work great on $5 vps without any issue.

Is fish better than Zsh?

It's definitely better out of the box, with no configuration. If you add a bunch of fancy plugins to zsh then they can be very similar

It's 'easier' for some people but you lose GNU bash compatibility and it kinda underlines all of the issues with interactive only shell systems -- a lack of interoperability.

It's honestly a non-issue in the current year to learn zsh or tcsh


I would love to use fish, but it seems there really isnt a oh-my-zsh equivalent.

I dont even need the OMZ prompt (i use starship for that), but the aliases from the kubectl and git plugins are just so great to have if you use kubectl and git often.

Other plugins (like colored-man-pages, fzf-tab and syntax-highlighting) are also nice.

Is there something like that for fish?

Oh-my-fish has some of those features, but it seems to be abandoned.


Integration with 3rd party scripts and tools is often a single line in your config.fish, something like `foo --init-fish | source` or better yet, `command -q foo && foo --init-fish | source`

We don't recommend oh-my-fish for various reasons, but I guess what's really missing is just a gallery.


[flagged]


Well you are in luck because that is exactly what the article is about.

??


Ha! I was in a hurry and misread this as the 4.0 release notes.

Me too. I just keep watching the crustea-culting and wondering if I’m actually missing out.

So read this article, which is about exactly this?...

Another rewrite? Hope it'll take off this time

Glad to see at least one Rust rewrite successful (unlike [1])!

[1] https://github.com/remacs/remacs


Another niche shell project that is going the way of the meme language. Nice.

meme language? seems you dont have much constructive to say :p

Meme language?

> What would test -x say on Windows, which has no executable bit?

It would say whether the file extension is executable (part of pathext env variable)


I should think it says the same thing as on Linux, since there absolutely is an 'executable bit' (GENERIC_EXECUTE) in the ACL.



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

Search: