You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: site/_posts/2024-12-25-rustport.md
+12-31Lines changed: 12 additions & 31 deletions
Original file line number
Diff line number
Diff line change
@@ -29,43 +29,27 @@ experience with a roughly C++-shaped language should help.
29
29
We've experienced some pain with C++. In short:
30
30
31
31
- tools
32
-
- slow-moving, but slower still until new versions are usable
33
32
- ergonomics
34
33
- compiler and platform differences
35
34
- (thread) safety
36
35
- dependency handling
37
36
38
37
Frankly, the tooling around the language isn't good, and we had to take on some additional pain in order to support our users.
39
-
We want them to have an easy way to get the newest version, and we want to have contributions even by people who aren't on bleeding edge systems[^Contributions].
40
-
That means we want it to be easy to build fish from source, and we want to build our own packages to have something to tell people who are on,
41
-
say, Ubuntu LTS and noticed that they are missing a cool new feature. We also prefer if we don't get reports of bugs
42
-
that we already fixed two versions ago[^LTS].
43
-
44
-
Doing that meant we could never rely on the newest C++ features. We started using C++11 in 2016,
45
-
and yet we *still* needed to upgrade the compilers on our build machines until 2020.
46
-
And upgrading the C++ compiler is annoying.
38
+
We want to provide up-to-date fish packages for systems that aren't up-to-date, like LTS Linux and older macOS.
39
+
But there is no 'rustup' for C++, no standard way to install recent C++ compilers on these operating systems.
40
+
This means adopting recent C++ standards would complicate the lives of packagers and would-be contributors[^Contributions].
41
+
For example, we started using C++11 in 2016, and yet we still needed to upgrade the compilers on our build machines until 2020.
47
42
48
43
Fish also uses threads for its award-winning (*note to editor*: find an actual award) autosuggestions and syntax highlighting,
49
44
and one long-term project is to add concurrency to the language.
50
45
51
-
Here's a dirty secret: fish's script execution is currently entirely serial - you can't background functions,
52
-
and you can't even run builtins in parallel. This code:
53
-
54
-
```fish
55
-
for i in 1 2 3 4 5
56
-
sleep 1
57
-
end | while read -l num
58
-
break
59
-
end
60
-
```
61
-
62
-
takes 5 seconds, because the `while read` loop can't even run before the `for` loop completes.
46
+
Here’s a dirty secret: while external commands run in parallel, fish’s execution of internal commands (builtins and functions) is currently serial. Lifting this limitation will enable features like asynchronous prompts or non-blocking completions.
63
47
64
48
POSIX shells use subshells to get around this, but subshells are a leaky abstraction that can bite you in the behind when you least expect it.
65
49
For instance you can't set variables from inside a pipe like that (except on some shells, but only in the last part of the pipe, maybe, if you have enabled the correct option).
66
50
We would like to avoid that, and so the heavy hand of forking off a process isn't appealing.
67
51
68
-
This involves a lot of careful handling of shared state, and C++ famously does not help - thread safety is your responsibility as the programmer.
52
+
We prototyped true multithreaded execution in C++, but it just didn't work out. For example, it was too easy to accidentally share objects across threads, with only post-hoc tools like Thread Sanitizer to prevent it.
69
53
70
54
The ergonomics of C++ are also simply not good - header files are annoying, templates are complicated, you can easily cause a compile error that throws *pages* of overloads in the standard library at you. Many functions are unsafe to use. C++ string handling is very verbose with
71
55
easily confusable overloads of many methods, making it attractive to drop down to C-style char pointers, which are quite unsafe.
@@ -104,7 +88,7 @@ Having an explicit `use` system where you know exactly which function comes from
104
88
105
89
Rust makes it nice to add dependencies. We don't want to go overboard with it, but we do want to change our history format from our homegrown "I can't believe it's not YAML" to something specified that other tools can actually read, and Rust makes it easy to add support for YAML/JSON/KDL.
106
90
107
-
And yes, Rust promises to help us with our threading problem.
91
+
But the killer feature of Rust, from fish-shell's perspective, is Send and Sync, statically enforcing rules around threading. "Fearless concurrency" is too strong - you can still blow your leg off with fork or signal handlers - but Send and Sync will be the key to unlocking fully multithreaded execution, with confidence in its correctness.
108
92
109
93
We did not do a comprehensive survey of other languages. We were confident Rust was up to the task and either already knew it or wanted to learn it, so we picked it.
110
94
@@ -114,7 +98,7 @@ A lot of hay has also been made online about Rust's platform support (e.g. [in t
114
98
115
99
Architecture support is even less of a problem - going by [debian's popcon](https://popcon.debian.org/), 99.9995% (the actual result, not an exaggeration) of machines run an architecture that has Rust packages in Debian. Given that fish is [installed on 1.92% of Debian systems](https://qa.debian.org/popcon.php?package=fish), we would project two (2) or three (3) machines of the quarter million responses to have fish on an unsupported architecture [^stats].
116
100
117
-
Unlike what some online have assumed, a native Windows port was not a reason for switching to Rust as it was never in the cards. Fish is, at heart, a unix shell that relies not only on unix APIs but also their semantics, and exposes them in the scripting language. What would `test -x` say on Windows, which has no executable bit? These are issues that *could* be solved with a lot of work, but we're unix nerds making a unix shell, not one for Windows.
101
+
Unlike what some online have assumed, a native Windows port was not a reason for switching to Rust as it was never in the cards. Fish is, at heart, a UNIX shell that relies not only on UNIX APIs but also their semantics, and exposes them in the scripting language. What would `test -x` say on Windows, which has no executable bit? These are issues that *could* be solved with a lot of work, but we're unix nerds making a unix shell, not one for Windows.
118
102
119
103
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.
120
104
@@ -151,7 +135,7 @@ That's how it went for a while, but we finally hit the more entangled systems, w
151
135
since that reduced the amount of tricky FFI code to be written only to be thrown away. These were ported in solo efforts.
152
136
This includes the input/output "reader", which is, unsurprisingly, one of fish's biggest parts, ending up at about 13000 lines of Rust.
153
137
154
-
During the port, we hit a bunch of snags with (auto)cxx. Sometimes it would just not understand a particular C++ construct, and we spent a lot of time trying to figure out ways to please it. As an example, we introduced a struct on the C++ side that wrapped C++'s `vector`, because for some reason autocxx liked to complain about `vector<wstring>`. It lacks support for wstring/wchar, which is understandable because using wchar is a horrible decision - we only do it because it's a historical mistake.
138
+
During the port, we hit a bunch of snags with (auto)cxx. Sometimes it would just not understand a particular C++ construct, and we spent a lot of time trying to figure out ways to please it. As an example, we introduced a struct on the C++ side that wrapped C++'s `vector`, because for some reason autocxx liked to complain about `vector<wstring>`. We had to fork it to add support for wstring/wchar, which is understandable because using wchar is a horrible decision - we only do it because it's a historical mistake.
155
139
156
140
Similarly, we had to wrap some C++ variables in `unique_ptr` and similar to make the ownership rules understandable to (auto)cxx, or copy values that didn't strictly need to be copied. This caused the performance during the port to go down quite a bit, but we regained all of it in most spots, and even beat the C++ version in some.
157
141
@@ -195,7 +179,7 @@ It won't surprise anyone who has spent any time on this world of ours that Rust
195
179
Chief among them is how Rust handles portability. While it offers many abstractions over systems, allowing you to target a variety of systems with the same code,
196
180
when it comes to *adapting* your code to systems at a lower-level, it's all based on enumerating systems by hand, using checks like `#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]`.
197
181
198
-
This is an imperfect solution, allowing you to miss systems and ignoring version differences entirely. From what we can tell, if Freebsd 12 gains a function that we want to use, libc would add it, but calling it would then fail on FreeBSD 11 without a good way to check, at the moment.
182
+
This is an imperfect solution, allowing you to miss systems and ignoring version differences entirely. From what we can tell, if FreeBSD 12 gains a function that we want to use, libc would add it, but calling it would then fail on FreeBSD 11 without a good way to check, at the moment.
199
183
200
184
But listing targets in our code is also fundamentally duplicating work that the libc crate (in our case) has already done. If you want to call libc::X, which is only defined on systems A, B and C, you need to put in that check for A, B and C yourself and if libc adds system D you need to add it as well. Instead of doing that, we are using our own [rsconf](https://github.com/mqudsi/rsconf) crate to do compile-time feature detection in build.rs.
201
185
@@ -208,9 +192,9 @@ We ported printf from musl, which we required for our own `printf` builtin anywa
208
192
209
193
### The Mistakes
210
194
211
-
We've hit some false starts, dead ends and other kinds of mistakes, for instance we originally originally used a fancy macro to allow us to write our strings as `"foo"L`, but that did not end up carrying its weight and we removed it in favor of a regular `L!("foo")` macro call.
195
+
We've hit some false starts, dead ends and other kinds of mistakes. For instance we originally used a fancy macro to allow us to write our strings as `"foo"L`, but that did not end up carrying its weight and we removed it in favor of a regular `L!("foo")` macro call.
212
196
213
-
We we were confused by a deprecation warning in the libc crate, which explains that "time_t" will be switched to 64-bit on musl in the future.
197
+
We were confused by a deprecation warning in the libc crate, which explains that "time_t" will be switched to 64-bit on musl in the future.
214
198
We initially tried to work around it, adding a lot of wrappers to try to stay agnostic on that size, but only later figured out that it does not affect us,
215
199
as we do not pass a time_t we get from one C library to another. (https://github.com/fish-shell/fish-shell/issues/10634)
216
200
@@ -285,9 +269,6 @@ And we had fun doing it.
285
269
both to get more testing and to take advantage of new features we introduce.
286
270
So we want to make this as painless as possible. This is working rather well, overall - we have over 1000 completion scripts in our codebase.
287
271
288
-
[^LTS]: The idea that LTS users should report bugs to their distribution is basically fiction. Not only does it not happen but it would also
289
-
be a terrible idea given that fish is in Ubuntu's "universe" repository, meaning it is imported automatically from Debian and otherwise almost entirely unmaintained in Ubuntu.
290
-
291
272
[^stats]: That is assuming that there isn't a correlation between running fish and using an unusual processor architecture. Also this includes Hurd and kFreeBSD.
292
273
293
274
[^technically]: Technically the first part of fish to be switched to rust is our [widecharwidth library](https://github.com/ridiculousfish/widecharwidth),
0 commit comments