First Impressions of Rust
john-millikin.com
john-millikin.com
Fair enough if that's not your preference for how parenthesized expressions should be broken across lines, but this quote makes it seem like it's objectively wrong. In fact it's very much a matter of opinion, and personally I hate the style of line breaking that he describes as "properly" aligned because you end up with a distracting pattern of indentation, and parameters get all squished into a tall narrow column:
x = long_function_name(first_param,
second_param,
another_fn(x
+ y
+ z))
Yuck! In Clang-format, you can avoid this with the "AlignAfterOpenBracket: AlwaysBreak" option, which also wraps the first parameter / operand to the next line if it can't all fit onto one line: x = long_function_name(
first_param,
second_param,
another_fn(x + y + z))
Just to reiterate: I appreciate that many people would still prefer the first style, and of course that's a legitimate opinion. My point is just that it's also a legitimate opinion to prefer the second style, not something you can describe as "not properly aligned".Of course this is a nitpick on an excellent article.
It turns out that most aspects of coding style are subjective and are fine once you get used to them. The *-fmt tools that are now the fashion get everyone past that initial stage of formatting things as you personally would and get you used to a common style.
Maybe one person (the author of the style tool) is completely happy with the chosen style, but consistent is more important than personal preference on details.
Edit: I'm particularly thinking of PostScript, Lisp and Python - which on first impression seemed crazy but I actually learned to really like.
It'll turn out that a lot of them were for a good reason, but some of them won't be and the existing team has just got used to them ('missing stair'). That's why the notes are important, because you probably have too!
That was a long time ago though.
I'd like to hear that story.
To create a new deployment, find an existing server (usually at an existing customer), take out a mirrored RAID disk and replace it with a blank one. When mirroring complete put old one back in and take your new copy and use it in a new server for the new customer....
Repeatable build process, repeatable deployment process - who needs that when you have the power of RAID-1! <sob>
Fortunately, it was a long time ago....
I remember there being quite a lot of community outreach to discuss pros and cons of all sorts of different rustfmt style options.
All of them were very thoughtful, and made what I think has ended up with a set of decent compromises.
There are cases where rustfmt is just bad. I'd argue "objectively" bad:
• match arms are formatted individually, so nearly-identical arms can end up looking wildly differently if they hit different fuzzy logic thresholds. In Rust, enum variants aren't types, and macros can't see enum variants in the match context, so repetitive match arms are sometimes a necessary evil.
• deletion of a line can fold a multi-line construct into a single line. This makes diffs bigger and harder to review.
• rustfmt throws away any human influence on layout of code (short of sorting functions alphabetically). It doesn't merely change the boring bikeshedable mechanics like where the spaces go, but significantly rearranges how expressions are laid out and separated from each other.
For example:
let value = do().something().to().get().a_value_maybe()
.or_else(|| some_error_handling_here)?;
I think the code above is great, because the happy path is on one line, and error handling is on the other. Rustfmt intentionally erases that: let value =
do().something().to().get().a_value_maybe().or_else(|| some_error_handling_here)?;
That is IMHO bizarre. The value is what? Oh, there's spaghetti in the second line.Gofmt is way better. It fixes formatting errors it understands, but when there are multiple correct ways to format a piece of code, it leaves the higher-level structure as-is. OTOH Rustfmt has an overriding opinion on everything. Non-negotiable heuristics replace all common sense.
There's no other production-quality formatter for Rust, so usage of rustfmt boils down to:
1. We must use something 2. Rustfmt is something 3. Therefore, we must use rustfmt
[1] https://play.rust-lang.org/
[2] https://play.rust-lang.org/?version=stable&mode=debug&editio...
Edit: I just realised the example I showed is different in that it doesn't try to line up all the field values (for fields that do fit on one line) so it must actually have some other option set, like you said.
rustfmt is not properly handling alignment in combination with hard tabs. It should either break after the open paren and indent both names, or break near the '+' and align them both.
Good output if aligned:
field: (value_1
+ value_2)
field: (value_1 +
value_2)
Good output if indented: field: (
value_1
+ value_2)
field: (
value_1 +
value_2)
Output of rustfmt: field: (value_1
+ value_2)
For the same expression to be half aligned and half indented is an error. field1: 1
this_is_a_longer_field: 2
Playing with rustfmt online [1], I found that by default it doesn't do that: field1: 1
this_is_a_longer_field: 2
As a sibling comment says, you must have an option turned on an option for this, and your complaint is that this option isn't working for these nested structs. I guess that's the risk of not using the formatter in its default configuration. But I agree it probably is a bug.(You've presumably also got an option turned on for tabs. I honestly think that is quite a niche preference nowadays - cue flame war - so I can see that there would be bugs in that option too. But although you bring up tabs in your comment here and in your blog post, I don't see that it's related to the bug you're talking about. Once the formatter has (incorrectly, you argue) decided to format those lines as "one more level of indentation than the line before", it chooses the correct combination of tabs and spaces to do that.)
[1] https://play.rust-lang.org/?version=stable&mode=debug&editio...
> You've presumably also got an option turned on for
> tabs. I honestly think that is quite a niche
> preference nowadays
gofmt always indents with tabs, so I'd argue that it's not _that_ niche.One might also argue that, as a zero-cost abstraction for consistently applying a developer's preferred indent depth, they are the most Rust-appropriate option </s>.
> Once the formatter has (incorrectly, you argue) decided
> to format those lines as "one more level of indentation
> than the line before", it chooses the correct
> combination of tabs and spaces to do that.)
I'm not sure that's the case. It seems to be correctly aligning it when using spaces, so I think the issue is that somewhere it gets confused about whether to indent or align. A lot of very old indenters will treat tabs and spaces as interchangeable, which is why you see so much poorly-formatted C in open-source UNIX tools.Indeed. If you're going to align expressions in a file that uses tabs for indentation then you need to use tabs up to the block indentation level, but spaces after that for the alignment. That's the only way to ensure that the lines continue to be aligned for any tab-stop setting. Many editors will mangle this horribly, however, by replacing some or all of the spaces with tabs. If you're using tabs for indentation then you really need to turn on visible whitespace and also disable automatic conversion of spaces to tabs unless you're sure your editor is one of the few that gets it right.
let result = (first_long_variable + second_long_variable) * (third_long_variable + fourth_long_variable);
My position is that the only way to make this look good under a line length limit is let factor1 = first_long_variable + second_long_variable;
let factor2 = third_long_variable + fourth_long_variable;
let result = factor1 * factor2;
(where of course `factor1` and `factor2` would be given names that make sense in context)Another note: The notion of a "compilation unit" in Rust is fuzzier than in C/C++, because of incremental compilation. The goal is to be able to point rustc at a crate and have rustc/cargo internally decide which parts of it should be recompiled and which can be cached. A good chunk of this is already done today—for example, rustc will internally split up single crates into multiple LLVM compilation units to get better parallelism during the codegen phase—but more can definitely be done. Basically, Rust leans more on "compiler heroics" than C/C++ traditionally has, in that the compiler effectively splits up source files automatically instead of the programmer manually doing so.
> You should be able to get the size of c_ulong without
> any dependencies using
> std::mem::size_of::<std::os::raw::c_ulong>().
That's very useful, thank you! I hadn't noticed `std::os::raw` before, but it's exactly what I wanted."I really really enjoy using Rust. It is nearly everything I want in a systems programming language, and the few parts it's missing are due to legitimate technical difficulty. The amount of careful thought I've seen put into its design – crafting async/await to work in no_std environments, or the new inline assembly syntax – has produced a language that is not only better than what I could have designed, it's better among axes I was not even aware existed prior to reading the Rust RFCs."
yes, that kinda sucks.
For a single crate, sure.
When you're managing multiple crates, an older nightly compiler version (because the software hasn't been tested much on the newer nightly), and git submodules... it isn't so easy.
I wasn't actually doing the work, but from the outside it seemed like a pain.
(I'm a big user of asm! myself, but other than that, stick to stable.)
I don't think that's really fair, or at least hasn't been true for a long time now. Use of the stable channel far exceeds use of nightly according to all the surveys. At my company we've always stuck to the stable channel, and even in my own personal projects where I have no need for stability, I don't remember the last time I used a nightly feature, simply because I haven't found the need to...
I don't believe there are many cases left where you're forced to use nightly: most of the cases where people are using it is because there's a "nice to have" feature, not because that feature is actually essential. Even rocket, which was famed for using a ton of nightly features, now works on stable Rust.
I've been doing some bare metal embedded projects in Rust and didn't need nightly for quite some time.
Of those, I could get away with just global_asm, start, and const_generics if I had to, but the others certainly make my life easier in a kernel space without an allocator.
I'll be the first to admit that some of those are "I'm just going to embrace nightly if I'm stuck on it anyway".
Creating a beta channel or getting more of those features released in stable requires money or time.
It's still a fairly young language - I think it's a good thing they're being conservative with not setting too much in stone prematurely, while still having stuff released as part of nightly to see relatively widespread use. As the language matures more and more you will see less and less important features that are missing from stable.
Compare to something like generics where it's roughly the same idea in most languages that feature them, you just need to learn the syntax and maybe some edge details that differ between languages (Contravariance and Covariance say hello!).
If you've ever helped someone who's entirely new to programming learn their first language you might see some parallels with experienced programmers learning entirely new features like lifetimes.
I would tend to disagree?
In C++, I regularly ran into issues where I "invoked" the complexity with memory/object management completely incidentally, and as the saying goes, shot myself in the foot (or rather, both feet, because lifetime management issues often result in heisenbugs and other really nasty phenomena).
Rust makes that complexity explicit, which is really an analogous argument as with static/dynamic typing; it's not that the dynamic languages "handle typing for you", and "static" simply requires more work. Rather, you're forced to deal with it at the very start, instead of down the road.
Then again I'm rather new to Rust, so I might misunderstand you?
A) Introduce burden on programmer
B) Introduce burden on runtime (GC basically)
You just get to pick your poison. Do you want the upfront cost in syntax (Rust) or do you want the down the line cost (C/C++).
In maven central, in my anecdotal experience, namespaces usually either distinguish forks of the same library, or to group related libraries. There has never been, in my experience, a case where I wanted two packages with the same name in different namespaces.
I think a better system of handling unmaintained crates; with the possibility of reclaiming a name would go a long way with the author's use case. I don't have any great ideas here: package with no update in some time (a year?) is eligible. Someone requests to take over name - the author is notified and has a period of time to click an "I'm still here" button. If they don't - new author gets the name. Some 'super version' number is incremented so crate authors opt into the new or old foo. That last part is where I'm the most hesitant.
"logger" is now forever a middleware for the iron framework. Same with "router". Iron is pretty much obsolete now.
It might have been good if the rust team themseleves had pre-squated
get rid of all the common names arguments and have everyone use prefixed or unique names
Reusing project names is just a straight no-go when that is what other peoples projects depend on. As soon as a package has a dependency, (name, version, source) need to be absolutely immutable.
it seems easier to do that for the common case then deal with the complexity of namespaces
In Maven anybody could publish a package that starts with "com.google". The namespacing proposals I have seen for crates.io assume that namespaces are exclusive to a single entitity though. So once someone that is not Google reserves the "google" namespace, Google itself would not be able to publish crates under that namespace anymore.
I don't think that's the case. If you want to get on Maven Central (at least via SonaType OSS) you have to prove that you own an email address on the relevant domain.
If it's your own private package repository, then sure, but then that's not an issue for anybody else.
So you could easily publish a package using the com.google namespace on your blog or whatever, but not on Maven Central.
Which definitely leads to an oddity in Java land - most libraries are in Maven Central, which requires some effort to submit to, but which is reasonably carefully policed, and some are in JCenter, which is easy to submit to, but is not policed. And then some libraries are in registries run by their maintainers.
Modern build tools make it easy to add registries. In practice, JCenter doesn't seem to have problems from the lack of policing. So this is very much an oddity rather than a problem.
> How do we decide which person/entity gets a namespace?
You get a personal one, named after your user account. In addition, you can create up to N namespaces. After that, you either have to request more or pay a small fee. This prevents namespace squatting, while still giving you the option to register a few for specific projects.
> What if there's a dispute?
First come first serve seems pretty reasonable. Just because your company is named X doesn't mean you have a perpetual and exclusive right to use that name wherever you want.
> What do we do about all the currently un-namespaced crates?
You would have to leave them, and perhaps disallow un-namespaced crates after a certain point of time. Maybe take a look at how NPM handled thsi.
> How would Rust (the language) understand namespaces?
A crate `foo/bar`, with `foo` being the namespace, should just translate to something like `use foo::bar`.
> Just because your company is named Microsoft doesn't mean you have a perpetual and exclusive right to use that name wherever you want.
Hmmm. :)
Do what maven does, and follow a reversed domain name convention. Or just first-come-first-served.
> What about namespace squatting?
It's much less of a problem, because having to use a different namespace is much less intrusive than having to use a different package name.
> What if there's a dispute?
What kind of dispute are you imagining? It's much harder for someone else's package name to cause a problem for you if packages are namespaced, since there's no way for someone else's package to end up in your namespace, whereas if you have to just use a convention like author-packagename then disputes are much more likely.
> What do we do about all the currently un-namespaced crates?
Either put them in a (deprecated) unnamed/root namespace, or turn each one into its own namespace (i.e. libfoo becomes libfoo:libfoo).
> How would Rust (the language) understand namespaces?
It doesn't need to, as far as rust is concerned a package is a package - they're handled at the cargo level.
> How would cargo work with them?
Dependencies would be (namespace, name, version) rather than just (name, version). And you just... do the sensible thing? This isn't a new idea, other languages have done this.
I don't think Rust itself needs to know about namespaces (as in: johndoe/fuse would still just expose a crate named "fuse". Crate name collisions can be handled with Cargo's already existing renaming ability.)
Yes, then the issue of namespace naming comes up. I guess that would be less of an issue than package-level collisions or squatting, but still, you don't want any random person to get the "google" namespace for example. Which could only be avoided by either manual review or something like DNS based verification (which has lots of drawbacks itself).
Maven is doing this to great success. The namespace issue has been solved successfully, just copy what they do. It took years until they arrived at the current system, and it works well. What drawbacks are you thinking of?
Any such RFC needs to take in to account previous discussions (and appreciate that the crates.io team is small). E.g.:
https://internals.rust-lang.org/t/crates-io-package-policies...
https://internals.rust-lang.org/t/namespacing-on-crates-io/8...
https://internals.rust-lang.org/t/pre-rfc-packages-as-namesp...
https://internals.rust-lang.org/t/pre-rfc-user-namespaces-on...
My proposal is to give everyone a namespace corresponding to their crates.io username, so if I want to publish a JSON library, it would be "user/majewsky/json".
Everything not prefixed with "user/" is a shared namespace which is handled by a team of curators. If someone wants to publish into the shared namespace, they need to apply for a package name. So if I think my JSON library should be the standard, I could apply for having it aliased to "json" or "encoding/json" or whatever. The curators would be allowed to impose an overall structure in the shared namespace to aid those who browse it.
Crucially, the curators should have the right to revoke an existing lease. If someone publishes their JSON library into the shared namespace as "json" and then later down the line abandons the project, other community members should be allowed to apply to take over that name. Of course, existing releases would stay untouched, but if I stopped maintaining my JSON library at 1.3.7, someone else should be allowed to take that over and publish 1.3.8 or later.
I would also impose the limitation that 0.x versions are not allowed in the shared namespace. If you want a nice package name, you should be ready to commit to at least a somewhat stable interface.
> How would Rust (the language) understand namespaces?
It's not hard to imagine a Rust that works with namespaced crates. What is hard is finding a solution that's backward- and forward-compatible.
The docker "hack" of using `_` is quite a smart hack here, and works well; for me at least. E.g. https://hub.docker.com/_/postgres its clear this is some "official" build.
> other community members should be allowed to apply to take over that name. Of course, existing releases would stay untouched, but if I stopped maintaining my JSON library at 1.3.7, someone else should be allowed to take that over and publish 1.3.8 or later.
This part is iffy: if you release a patch, it implies that it has backwards-compatible interface. So this will realistically only work for taking over the maintenance of the old package, not for replacing it with something entirely new. I think a version epoch might work for the latter.
(Side-note: I'm only talking about libraries here, not applications.)
It's a bit more consistent in that no-one gets good names, but is it really such a problem that a few people happened to get in early enough that they could call their library 'fuse'?
"rust-" or "-rs" is useless because it's on crates.io. "lib" or "-lib" similarly, unless it's part of a pair with a binary of the same name.
Here's a partial list of FUSE server implementations on crates.io:
* fuse
* cntr-fuse
* drakey-fuse
* fuse_mt
* fuser
* fuse-rs
* polyfuse
* fuse3
* yarf
Why do we make people come up with custom prefixes or opaque codenames when they could all be ~user/fuse ?A flat "top level" namespace encourages treating names, especially short names, as something with intrinsic value. You end up with the same problems that the .com namespace has, such as name squatting and first mover advantage.
Qualifying all names removes this property from names and levels the playing field. There's no value in "owning" github.com/somename/blah. Or, to take the NPM approach, @somename/blah.
username_logger, username_fuse, etc.
?
A lot of the supposed benefits seem like downsides. The ability to have
bob_ward/docker and lilly_smith/docker seems like a misfeature
I guess you could argue it makes to avoid malicious packages w/ similar spelling but then they could just try the same thing with even less suspicion w/ similarly named namespaces
> It's not obvious to me why they do this – it's a documentation generator, why does it care what version of the Rust compiler I'm using?
rustdoc, being a part of the Rust distribution, has the same stability guarantees that Rust does: once it gains a feature, it will never go away. We (well, not me I don't really work on rustdoc, to be clear) need the capacity to try out new features without committing to them, same as features in the language.
> PyPI launched in 2003, and CPAN has been running since 1995
Neither of these support namespacing though, right?
> Cargo's unit of distribution is the crate, which is a problem because Rust's compilation unit is also the crate.
Cargo's unit of distribution is a package, which is one or more crates. It is true that there can only be one library crate per package, however. This is one area where people don't use the correct words super often, honestly.
> Of course build times are slow if changing one line of a leaf file requires rebuilding dozens of modules.
This shouldn't change based on if you're using Cargo or not; if it's re-building dependencies every time you touch a file, that is a bug.
> rustdoc, being a part of the Rust distribution,
> has the same stability guarantees that Rust does:
> once it gains a feature, it will never go away. We
> (well, not me I don't really work on rustdoc, to be
> clear) need the capacity to try out new features
> without committing to them, same as features in the
> language.
I guess I expect rustdoc and rustc to be much less tightly coupled, so that I could use (for example) stable rustc and nightly rustdoc on the same code.In the current model, I can't use nightly rustdoc features without also using nightly rustc, because the annotations are processed by both systems.
You could imagine an alternate model where rustc stabilizes `#[doc(...)]` and leaves everything in there up to rustdoc's interpretation, and nightly rustdoc might enable new parameters to the #[doc] annotation.
> Neither of these support namespacing though, right?
That's correct. I meant that mostly in terms of "package registries are not a new and untested idea". It's like using strings for file paths: not obviously a bad idea, turns out to be unworkable for obscure reasons, and as a new language Rust got to avoid that particular mistake. It feels like crates.io didn't (/ doesn't) have a similar process of avoiding known mistakes. > This shouldn't change based on if you're using Cargo
> or not; if it's re-building dependencies every time
> you touch a file, that is a bug.
Given a crate with a hundred files, each one fairly independent of the other (i.e. a broad shallow build graph), changing one requires (if I understand rustc correctly) recompiling them all. Otherwise the `crate::` references can't be type-checked.However the tricky bit is that the kernel doesn't enforce this so it's possible for programmers to intentionally make broken UTF-16 strings. A broken UTF-16 string shouldn't be considered UCS-2 just because it happens to be a valid UCS-2 string (otherwise all bit patterns could be called "UCS-2" so long as they are an even number of bytes in length).
The first bad option is to say "paths are bytes", which is true on traditional POSIX platforms. Then you run your code on macOS and discover it's processing your bytes as UTF-8 and applying Unicode normalization. After fixing that you try porting to Windows and get to learn about `wchar_t`.
The other bad option is to say "paths are (Unicode) strings", which works fine on Windows and macOS. It works 90% of the time on POSIX, but you'll eventually find users with non-UTF8 file paths and then you're in trouble.
My first hand experience with this going wrong is GHC, which started off with raw bytes, then moved to strings using a bad conversion function, then they tried to work around the issue by placing non-UTF8 bytes into a Unicode reserved area.
Java also has problems because it represents paths as strings and tries to use the libc locale for decoding. https://github.com/bazelbuild/bazel/pull/10111 is an example of how this breaks software in ways that are difficult to work around.
But that doesn't really solve the problem. You still have to convert file paths to strings to print them, and strings to file paths to accept them as from the terminal and databases. For that to work the round trip had better be safe on any given platform otherwise the user can't copy and paste.
Not addressing this problem has been python3's undoing when it is used as a shell scripting replacement. Actually, it's an undoing any time you want to treat "text" as interesting ascii swimming in an unknown encoding, particularly when you want to modify that ascii. HTML with an unknown encoding is a prime example.
Yeah, and that could work in theory. The thing is, rustdoc uses the compiler as a library, so they actually are pretty tightly coupled. This causes a lot of pain but also has a bunch of upsides. I really want to see a rustdoc that is not, but it's gonna take a long time.
> That's correct.
Cool, I thought so, I just wasn't 100% sure! I think it's interesting how different people take away different things here, to me, this demonstrates that you don't have to have namespaces; many of the largest ecosystems do not and have not had them. We did learn a lesson there, just didn't come to the same conclusion that you did. This one is more controversial within the community though, many people do agree with you.
> Given a crate with a hundred files
Yes, but that doesn't change with rustc vs cargo though; it's still a single rustc invocation to compile all those files. (And, it doesn't quite do so, there is some degree of incremental compilation going on, but it's not as fine-grained as it can be. Working on it!)
> Yes, but that doesn't change with rustc vs cargo
> though; it's still a single rustc invocation to
> compile all those files.
When using Bazel instead of Cargo the developer can split the build graph into much smaller crates (down to one per module if desired), and still treat the entire assembly as one package.I should emphasize that this is a limitation of Cargo, not rustc. rustc does just fine with big libraries, it's only Cargo that makes it difficult to write hundred-crate packages.
(Sorry, I feel a bit dense here, but I am very curious to understand exactly. I haven't used Bazel a ton yet, but am very interested in it.)
Ideally I could have a single crates.io package, with a single tarball, containing multiple crates. The internal crates would not be exposed to users, wouldn't have versions, wouldn't be on docs.rs, and so on.
I do think that's an interesting idea. I'll have to mull on it. Thanks again. I've been thinking of a few things here, very interesting :)
Maven is from 2004 and has namespaces. Maven has it's problems, but the Java package management ecosystem gets many things right that later systems have not.
(And the lesson isn’t that either, the claim was never “this feature is terrible and ruins the language”, there very much are successful examples. The claim is that the lack of the feature does not mean failure.)
I didn't mention it because -- and this may be rude to Maven -- I consider Maven's coordinates syntax to be closer to "primitive Go" than it's own distinct thing. If software hosted on `example.com/foo/bar` has the package name `com.example.foo/bar` then it's introducing ambiguity without much benefit.
Surely that's only true at any one instant? Domain names change hands all the time.
For example: All non-EU citizens in the UK that have .eu domain will have them forcibly de-registered at the end of the year. What happens to a eu.example.foo package then?
> Neither of these support namespacing though, right?
But both have policies that prohibit name squatting, whereas crates.io has a policy which explicitly allows it.
Even though it's a bit ugly, this is a big win.
As for why it does this, from my experience, rust-fmt will try to keep lines under a column limit but not greedily. e.g. if a params list would extend past the limit, then all params get a newline. It seems to try to balance horizontal estate and vertical estate and I've become used to that (even though it is inconsistent at times).
Any formatting rule is going to fall over eventually. Rust-fmt seems to make sane choices the majority of the time.
When I worked in the defence industry (~15 years ago now), a lot of the coding standards for C/C++ (which was starting to be used more and more over ADA at the time) were very strict on the matching alignment of things like open / closing braces, so things like hanging braces were not allowed, both opening and closing braces had to be on their own lines.
The argument behind this was it made the code clearer, and more easy to match clauses / scoping - in a way arguing it was "safer" in terms of interpretation of the code.
The argument to match clauses/scoping for example is weird in a world where a formatter automatically applies indentation. It might make sense when you have to manually check them on print-outs without an Editor. I'd argue GNU style fulfills that role even better or Horstmann if you care about vertical real estate (or be daring and combine the two). And now everyone hates me.
In a way that I tell the requester "you better have some really, really good reason for wasting my time on this".
Hell, I've even had a new hire prepare an entire presentation to convince us to change the line-length to something different than "The Language Standard".
"We follow the language standard" and if you don't like it, convince upstream (This was Rubocop/Ruby) is another one I've used in the past.
Naturally both people just gave up. There really is a better way to spend time than on tuning and tweaking a linter. Nearly all linters have some way to document and define very local exceptions for that case when some external__dependency_IsMxing_camel_case or whatever.
Rust's formatter not making up artificial alignment columns is the best thing about it.
[1] https://rust-lang.github.io/rustfmt/?version=master&search=
I've had to make use of `rustfmt::skip` in various places to keep it from doing things like mangling the groupings and explanatory comments in `const` arrays or making code gratuitously tall and thin.
...and that sometimes means either `git gui` cherry-picking and only running rustfmt infrequently or applying `rustfmt::skip` too broadly because of errors like "error[E0658]: attributes on expressions are experimental"
(I'm in the process of migrating from cherry-picking to over-broad `rustfmt::skip` so I can use format-on-save.)
I just don't think it's a big win, but I've found it's a hard rhetorical battle to fight. Everyone's more cynically fearful of ugly code rather than having that extra leeway to write extremely readable code. I've definitely found this can throw the baby out with the bath water.
And that ignores the synergistic benefits of erasing all style-based co-ordination problems & arguments & bikeshedding & whatever else from the entire codebase. IMO it's a no-brainer.
Exactly this. I sat across from the poor soul who was assigned to creating the coding standards for our C# stuff. It tooks months. Nobody was happy, everyone had their own way of doing things: var vs. no-var vs. obvious-type-var, CONST_VALUE vs ConstValue. The work has been completed (the person in question quit soon after), people are still unhappy, there are still (and will always be) open questions. People nit coding standards in code reviews instead of looking for real issues. What a shitshow. If you remove autonomy concerning coding standards from the get-go, people never form these opinions.
If code is not optimally pretty, that sucks. Establishing coding standards is an bottomless pit of despair. Almost any opinionated formatter is worlds better than the worthless sisyphian task of establishing your own coding standards.
As someone observed above it's the only win you are likely to get, as ugly often boils down to "something I have seen before".
There a few idioms that are actively helpful or harmful. For example, python making whitespace significant neatly meshes with human visual system. We are really good a picking up changes in indentation, not so good at lining up curly brackets. But best of luck trying to get 1/2 the population to accept that, even though it's objectively accurate.
On the other side, allowing too many levels of operator precedence seems to be harmful. I loved the conciseness when I was young. It was only later I noticed people struggled with some of my code because of it. Pascal (I think?) solved the problem by making combining some operators without parenthesis a syntax error. But since I only used Pascal when I was young, I hated it whenever I did use it.
So yeah, consistency is the only easy win. And as you say it's a big one: consistently formatted code is much easier to read than a miss-mash.
Rust actually supports sealed traits by using public traits in private modules. See this for how to use it, and how it works:
https://rust-lang.github.io/api-guidelines/future-proofing.h...
* It's not understood by rustdoc, so I have to manually document that the trait is sealed.
* It technically violates Rust's rules against private symbols in the public API, so a future version of rustc might deprecate or remove that functionality.
[0] https://github.com/jmillikin/rust-fuse/blob/a6ad16d1127d36f8...
Are you sure about this? My understanding is that stable Rust limits itself to compatibility breaks that are both rare and trivially worked around. (Like adding a new inherent method to a standard type that happens to have the same name as your trait method.) Even in a new edition, I think there's a very heavy leaning towards changes that can be automated by `cargo fix`. Removing this idiom seems like it would be much too big of a change. (The obvious automatic fix -- just inserting `pub` as needed -- would presumably make a bunch of currently safe APIs unsound.)
It's public though, just not externally reachable, the private module doesn't make the trait private.
For example, if you wanted to, you could reexport the trait in the parent module (and I guess the drawback with the "sealed" pattern is accidentally doing that when it would be unsound do because of how you relied on it being "sealed", in unsafe code).
I don't think I've heard anything about plans to restrict anything based on reachability, and it would be massively backwards-incompatible so I doubt it would even be considered for an edition.
First is completing the `impl Trait` saga. I see a couple of areas where I'm obliged to box a trait to get the compiler to accept the code. Not the end of the world.
Second is allowing conversions of AsyncRead to Stream. currently you have to do a weird dance to get chunks of tokio::File as Stream of Bytes. I think something is missing. Something as straightforward should be easy even if it's not part of std.
Last, is adding support for Generators and their transformation into Stream. Then, getting a Stream of Val would be as simple as `yield val` in a function. I'm sure their current API on nightly will change before it's merged to stable rust, and I even hope they will get rid of the "return type" in gens (it complicates things a little bit).
After that last point, I can see Rust replacing python for most of the data prep work we have in data science/AI.
Re: crates namespaces, that is a crates decision, not necessarily Rust'. You can always publish your crate on github and include it with "package = { git = ... }" and you'll get the same thing as in go.
* Used in the vast majority of Rust programs.
* Reasonably common things that require a lot of unsafe to implement.
* Traits for interoperation purposes.
HashMap and mutexes fall under #2. The idea is that they'll receive significantly more scrutiny, and this is a good thing for the ecosystem.
(These are guidelines, not hard and fast rules, so not literally everything fits here, but that's the framework that was used to think about what should be included or not, generally.)
Mind you, increasingly parts of the std are more like wrappers around third party crates e.g. hashbrown and parking_lot.
Sorta kinda. Crates are a Rust language concept, and they do not support namespacing. You'd have to hack around that to get it to work.
https://mstdn.io/@jmillikin/103158451151123313
I'll put this on the list of things to fix next time I go digging at the stylesheet.
The simple fact is Cargo did not learn from prior art to the extent Rust did.
Things have gotten better, but Rust is still better designed. Hopefully Cargo can someday catch up.
What about a public type with no public fields nor public methods? Doesn't that accomplish exactly what the author is looking for?
It turned out to be insufficiently expressive to put into libraries the semantics I want to encapsulate. I went back to C++, and have been very happy. (It's also nice that lots of people want to pay to have it done.) As with Rust, I can write 2000 lines and, once it compiles, it works.
The hardest thing about going back to C++ from Rust was getting used to putting semicolons where Rust doesn't require them. Years later it still trips me.
So, overwhelmingly the bugs you do get are specification bugs: the code does what was asked, but the ask was wrong. The only way to avoid those is to pay attention. Anything that steals attention generates them, in C++ as in Rust.
Old, pre-C++11 codebases (and codebases still written that way, or in C-with-classes style) suffer more. Mozilla and Google have a lot of old code.
The primordial enabler in '98 was template partial specialization, that made the STL possible: same name with different types uses a different implementation.
If anyone is interested in another alternative to Cargo that doesn't involve Java, there is build2[1] and it's libbuild2-rust[2] module. It's a general-purpose build system so you can have mixed-language projects, etc.
Uhm, ok, pointers in Haskell… must be really low-level programming, because ordinary Haskell doesn't deal with pointers.
My Haskell binding to CPython is larger but of about the same shape.
The basic complaint boils down to "packages aren't namespaced" and "someone already took the 'fuse' package", which has been a long running controversy for exactly that name-squatting reason.
However, Cargo is great.
> Rust's default build system (Cargo) and package repository (crates.io) are the opposite. They combine the worst parts of Cabal/Hackage and NPM, resulting in a user experience that is somehow inferior to both.
No. They don't. That's your opinion, not a fact, and I completely disagree. Lots of people disagree. Your arbitrary assertion is unsubstantiated and I reject it. :) Perhaps you meant "resulting in a user experience that I found somehow inferior to both".
If you don't like it, and you prefer to use bazel, then by all means be welcome to do so... but I think a little bit of acknowledgement is in order that ... frankly, that behavior should be discouraged.
We want a unified good build system for rust, not a ridiculous mess of difference package managers and build systems like in some other language ecosystems. Sometimes, conformity is a better approach, and I would be deeply saddened to see rust go that way.
I'm very impressed with the efforts to bring wasm support to cargo, and the other initiatives.
Be nice. Lots of people work really hard on cargo. They're doing a great job.
I would like to disagree on this. I would prefer to see the Rust ecosystem play better with other build systems, instead of requiring all Rust to use Cargo. This becomes important when you are using Rust as part of a larger, multi-language code base. At my day job, we have Rust, Python and Typescript, and we use Bazel for much of the build, getting a bunch of advantages that Bazel brings to the table - remote caching, being able to query the dependency graph, selective testing and so on. These latter are not available in cargo, and would be significant work to re-implement infrastructure based on querying cargo instead of querying bazel.
I think there are 3 different things that Cargo tries to do, which would be better of with strong (and well documented) boundaries between them, so that users could mix and match each of these.
1. Compiling a crate - This is usually one rustc call. I'd venture saying that build.rs files are another layer, but I haven't thought about it enough. It is pretty common in Bazel-land to have build.rs wrapped in a `rust_binary` that is a dependency for the crate itself (usually `rust_library`). This is the easiest to plug into other build systems. 2. Compiling a set of crates/packages - Assuming a system where all the crates are on-disk, Cargo does the job of invoking rustc correctly with the dependencies passed and the right set of features selected. For Bazel, this is what rules_rust implements. The thing is, rules_rust has to figure this out by inspection of Cargo, instead of being able to rely on editions or compiler versions. (FYI, I've never used rules_rust. We have our own internal rules for Rust and we had to figure out things by inspection. Particularly Cargo specific environment variables that are not a part of the Rust language/stdlib, but are assumed to exist by libraries). 3. Package management - i.e. how to get dependencies from the network/someplace else onto disk. I'd suspect this is one place most large orgs would prefer not to rely on Cargo, because it necessarily locks them into some kind of delivery mechanism that is specific to the ecosystem, instead of having a dumb file server. i.e. we would need one for npm/yarn, one for pypi and so on, where each of these frontends know how to go from the "language package name" to the file on disk. I much prefer Bazel here where external dependencies are specified just by HTTP URLs and a sha256 hash to verify contents.
I'm almost _not_ on board with each language building their own compiler driver and package manager, because they end up duplicating so much work - some kind of caching mechanism, some kind of toolchain mechanism, some kind of resolver mechanism and so on...
I first noticed as scrolling doesnt work with the keyboard. Upon inspection of HTML hes using weird shadow dom stuff.
Why do people do stuff like this? Its a static site, modern HTML and CSS are huge and can handle nearly any situation.
I firmly believe that you shouldn't need client-side JavaScript for anything fancier than things like progressively enhancing a form field into a clickable star-rating widget unless you're writing an actual "web app"... definitely not for a blog. I'm just glad the text of his articles remains readable under the influence of my default uMatrix settings, even if none of the site-navigation boilerplate is.
Heck, if I thought it could be done without failing Lyndon Johnson's "the harm it would cause if improperly administered" test, I'd ask for laws that penalize people for pushing that sort of computating to the client where it can't be cached for more than just that one machine. They could be justified as a way to cut down on CO2 emissions.
As-is, if I can ever find the time, I want to see how much performance gain and "model has desynced from visible rendering" resilience I can get in my Firefox by ripping out Tree Style Tab and replacing it with something that uses the sidebar's DOM as the authoritative data model, built using <details>/<summary> expander/collapser nesting. (I've already prototyped the HTML for that as a simple transformation of what Markdown or ReStructuredText renderers output for nested <ul> outlines.)
The shadow-DOM thing svnpenn commented on is a red herring. It doesn't interfere with keyboard scrolling, which works perfectly well. The shadow DOM is merely used to isolate CSS styling rules so that they apply to an instance of a component rather than sharing a single global namespace. The elements are still rendered as part of the parent document.
I think there are very smart older programmers who don't appreciate this fact properly. They sort of have a belief that there does not exist a "sufficiently good" good code formatter for all users -- and so therefore code formatter should have tons of configuration options to satisfy a range of style choices.
I think that opinion belongs in the past (with the discussion of tabs vs spaces). We have witnessed the existence of sufficiently high quality opinionated code formatters (java, gofmt, prettier, ...) -- and these code formatters without style choices produce a better world.
The way to get there is to find and prefer the non controversial formatting choices -- that means style preferences are abstracted away and consensus building grows from examples of code that everyone agrees is wrong and wants to fix.
The rust formatting issue he highlights makes me really annoyed -- the format chosen by the auto formatter for that sample code is not the best for that situation -- and I'm pretty sure it would be non-controversial for folks to agree on that ... am i right?
I'm doing some swift in xcode after awhile using an ide that lets me have "reformat on save" and the experience of "reformat on save" is good enough that I think it probably should be adopted as a first class goal of language designers -- there should be a language level "reformat on save" story that language designers assume will be used ... Baking in reformat on save as an assumed feature at language design time could allow creation or recovery from "write only" syntaxes -- (nice to write but ambiguous to read) -- either added on purpose or by mistake. Would also allow for really powerful Language evolution cleanup opportunities and the opportunity to introduce semantic changes where old functionality is preserved with complete compatibility until the source file is interactively edited from within an editor (eg the semantic change produces a code diff) ...
If the language designers assume the capability exists and will be used -- they can specifically evolve the language based on this assumption. All thats needed is a super high quality, compiler-provided implementation of the functionality -- and to set the expectation -- "sorry you have to wire this functionality up -- otherwise your code is going to be more likely to break if you edit the source files over time while also upgrading your compiler over time."
I'm benoaning the fact that no languages I'm aware of have advertised such a position as part of their evolution plan ...
To "save" some code means that an application writes it to hard drive. But writing to a hard drive is entirely orthogonal to the operation of a compiler. For example, a compiler should be able to compile code that is never written to hard drive -- exists only in memory. The hard drive is just one possible source of code to be compiled.
I do agree with you though that autoformatting code on save is a fantastic experience. It works beautifully with Rust, and so now I do it (less beautifully) with Python/black. I just think that although it's a great developer experience, it's several steps removed from anything that anyone would want tied to the compiler.