> "Interesting", you think, "I didn't know that Vec has a write_u16 method". You quickly check the documentation - indeed, it doesn't! So where is it coming from? You grep the project... nothing. It's nowhere in the imports. You examine the imports one by one
> [...]
> It's entirely possible that using a language like Rust without a sophisticated IDE is madness, and I'm somewhat stuck in the past. But I have to say, I do lament the loss of greppability.
Seems more like Eli has completely missed an important and integral feature of the rust ecosystem: `cargo doc`.
While the stdlib sadly remains out of it (long-standing RFC 2324[0]), cargo doc will otherwise generate the documentation including all dependencies by default.
This means you can in fact get this information by "checking the documentation", just the right one: a global search in the project's `cargo doc` will quickly tell you where the method comes from[1], without the need for any IDE or complicated integration (though I don't think it works with text browsers as it uses javascript, for terminal / console contexts maybe try `rusty-man`, I don't know how good it is tho).
I couldn't imagine working on a Rust project without having its `cargo doc` permanently open in a tab, having the unified offline documentation for all your dependencies is just way too useful, I run `cargo doc` almost as often as I run `cargo check` (despite that being mostly useless for binary crates...)
[1] just tried it on a local project, the first methods it surfaces are 4 methods from byteorder (on ByteOrder, WriteBytesEx, BigEndian, and LittleEndian) and one from serde_json (on Formatter).
Good point, and for those unaware, cargo doc also shows you all of the traits defined for a type including those from other modules. You can even quickly see the source code defining the trait implementation. Rust has some of the best documentation of any language (which to be fair it really needs due to all of its quirks).
When someone accuses Java of being an IDE-only language, I guess I'll use this argument as well because Java also always had javadocs which would allow you to see all type hierarchies, inherited methods etc which make Java, similar to Rust, hard to read without an IDE.
I must have missed something but stdlib's javadoc is so horrendous. Yes it has exhaustive list of methods but it's devoid of actual programming content. No clear definition most of the time, no examples, no diagrams, no properties .. I swear on every remaining hair, everytime I have to visit these pages I go depressed. It was so much so that I wrote a json doclet to try to embed it into a repl to be able to experiment with actual objects with a bit of completion rather than having to waste precious time.
What’s the easiest way to access the javadocs for the transitive dependency closure of your project? I didn’t know about cargo docs until now, but it seems way more useful than javadocs. An good IDE still seems better than both, however.
The other path that works well is to use Dash/Zeal for documentation.
I find dealing with a browser tab to be a bit awkward, especially if I'm working on a Rust project using Wasm and actively testing in a browser and having to flip between docs and my app. Having a dedicated documentation viewing app that I can show/hide with a keyboard shortcut just saves a second or so every time I need to pull up docs. And the integration with IDEs can sometimes mean I don't even have to leave my editor to get the answer I need. It also integrates documentation for std/core with docs for crates I'm using giving me a single search of everything that my Rust code can use. The only downside compared to cargo's docs is that it doesn't document my own code that I'm working on, but it's much easier to remember the details of that code.
The cherry on top is that it isn't limited to Rust. If I'm working on something with an FFI dependency, I can have the same documentation workflow for C/C++ documentation. If I'm working on a Wasm project, I can pull up HTML/CSS documentation the exact same way. All my documentation lives in the same place under the same keyboard shortcut.
Looks like Rust is missing something like hoogle. The language's types are barely powerful enough for it to add value, but it's certainly on the "adds value" side of the divide.
rustdoc does support a hoogle-like syntax (in a rustdoc page click on the question mark next to the search field for shortcut and "search tricks"), sadly it's not very good.
Rust does add a few wrinkles due to its "type policy" being less regular though e.g. how you handle `self`, as well as the various types of references.
For instance let's say you have
impl Foo {
fn foo(&self, p: &Path) -> usize
}
Does this match `Foo, Path -> usize` or `Path -> usize`? Or both? Or neither (because references). This also outlines a second issue which is whether `Deref` should be involved in the search (so e.g. should `PathBuf -> usize` find this).
I'm actually aware of cargo doc - it's really useful, indeed. It's still note quite "greppability" though; I can open `cargo doc`. I can just open the browser and google "rust write_u16" and immediately find it. I can keep VS Code open with my project and search for it there. But neither of these is greppability :)
Cargo doc is nowhere near as useful as something like go doc, where giving a symbol name immediately prints plain text documentation in the terminal. Cargo doc is just… inconvenient in comparison.
Not only is that not relevant (that two commands have the same name doesn’t mean they have the same purpose), as far as I know you’ve managed to bring up “go doc” in a context where it is useless: if you give a symbol to `go doc`, it’s going to search that in the current package, which for the purpose of TFA is no more helpful than `grep`.
That go doc does not currently handle rust traits, a feature that has no equivalent in Go, is a pointless observation. Rust doc shows traits together with the class they are implemented for, so a “rust doc std::vec::Vec” should print them too.
The point is that Rust is behind Go on convenient documentation access, which is ironic seeing that Rust requires documentation access much more than Go.
go doc also accesses all documentation for packages used in a project, making it do quite a lot more than grep.
As far as I know you are, once again, completely wrong. `go doc` looks up symbols in a single package, which is the current package by default. That is what go doc's own documentation states.
If you had tried using it or otherwise knew what you were talking about you would know that you use it by specifying relative or absolute import paths for any package fetched with go mod or $GOPATH.
Just in case this needs an ELI5: it queries project packages to print the relevant documentation for any accessible component: “go doc container/list.List” - bar any typos from a phone keyboard - prints all documentation for the stdlib linked list. In case Rust had a decent doc tool it would be the equivalent of “rust doc std::collections::LinkedList”.
(And no, that the Go tool needs to do less work to find all relevant documentation than a Rust equivalent is not relevant to the discussion.)
Thank you for once again proving the point which you keep missing: you have to give `go doc` a path to a module, which means you have to know where the symbol you’re looking up lives.
Which has nothing to do with the article, or my comments. The entire issue of the comment is that Eli has a symbol whose origin they don’t know. Where the symbol comes from is 75% of the question.
Of course if you want the documentation of a symbol whose full qualification you know `go doc` gives you that, that’s what I wrote above.
But it’s not TFA’s problem, and thus not the problem anyone’s trying to solve here.
It’s really hard to care about people who consider waiting for a webpage to build, a browser to load and a search to be typed as an acceptable solution to documentation access.
That argument doesn't work both ways. One group has far more stringent requirements than the other as evidenced by your use of the word "acceptable".
Web documentation is easy to produce and is acceptable to many. All other kinds of documentation can also be converted into web documentation, therefore you do not have to spend extra effort for it.
Terminal documentation is more difficult due to the limitations of displaying things in the terminal, therefore it requires more effort.
I certainly agree that those who prefer terminals are a subset of developers - despite this missing the point of terminal-friendly documentation also means generally reusable and integration friendly, unlike a full web application - but terminal documentation is objectively orders of magnitude less work than writing web app for showing documentation.
Terminal documentation just means plan text. You print it, that’s it. Making it pretty involves indents and line wrapping. Any source formatting is just ignored.
Fwiw people aren't building the webpage on demand. They're doing it on entry to the project, and so it's already ready when you want it.
Rust takes forever to build too if you've never built the project before, but most people also build that ahead of time too. Repeated doc usage and repeated builds/checks don't require the "full build"
What use case is that? FWIW, the docs are already generated programmatically in the first place, so you could access them with a similar approach (not sure how difficult that is, so maybe it's unrealistic advice).
Also there's a tool (forgot the name now) that can render them in the terminal similar to manpages.
If there is a terminal rendition tool, please share. Last I checked all efforts where abandoned, either due to too tight integration to rustdoc or due to bike shedding…
Note that there is no problem with the web version being available, it’s useful at times.
Honestly, I hadn't considered that (and I've upvoted) because I just use "search" in the rust docs. But I can see some value in grep'ability of docs, even if for me it has never really come up.
I suppose it would be nice if rust's docs supported a full text search.
I guess it comes down to taste and preference, but I much prefer cargo doc to go doc. Go doc has a "brutalist/minumalist" approach (I understand some people prefer that), while cargo doc has a more "humanist" approach to the docs. I want all the bells and whistles in the browser docs like making it easy to search, read neatly formatted/rendered text, links, and syntax highlighting in the code snippets.
> While the stdlib sadly remains out of it (long-standing RFC 2324[0]), cargo doc will otherwise generate the documentation including all dependencies by default.
It's probably mentioned in that issue, but I'm sure I read recently they want to crate-ise `std`; at which point presumably `cargo doc` would show it like anything else, wouldn't even know the difference? (That was even the motivation iirc, just for a different part of cargo, not doc.)
FWIW I personally think 5 possible matches is potentially already too much if you're a newcomer to a language or even just a large project. If you're willing to give up ad-hoc polymorphism (a big ask, but possible!) then in theory you don't even need to search outside your current file (given explicit import syntax, i.e. no wildcard imports).
> FWIW I personally think 5 possible matches is potentially already too much if you're a newcomer to a language or even just a large project.
I'm... not sure what you'd want to happen when there are literally 5 methods called write_u16, how is a general-purpose search to know which one you're looking for without more information? If you want contextual matching... use an IDE (or hook rust-analyser into your editor of preference).
(also there are way more than 5 matches in total as there are methods like `write_u16_into` which would also match at a lower rank).
Without ad-hoc polymorphism and with explicit exports the correct one must have been disambiguated in import statements or must otherwise have been disambiguated at the call site via a namespace prefix (negating the need to do any searching at all).
So simple ctrl-f in the file would do (or the call site would identify it).
I've no idea what you're trying to say, but as far as I can interpret it when it comes to Rust you got things exactly backwards:
foo().bar().baz()
if everything is monomorphic, there is no requirement that the result of bar() be a member of a direct dependency, to say nothing of being imported into the local scope.
If `baz()` is a member of a trait, however, that trait must be in-scope.
I'm talking about a hypothetical other language without ad-hoc polymorphism (in the spirit of the original article comparing Rust against other languages such as e.g. Go) and also in this case without methods. One poster child for this kind of programming is Elm (there are several other even more niche languages that do this as well).
I think it's fair to be sad that greappability is being lost. But I also don't think we should limit ourselves to it; treating it as a design goal holds back the field. Languages can be much more powerful when they drop that constraint and assume the reader has (language-aware) tooling available to help them make sense of code.
I think instead we should focus on continuing to push the tooling forward. Language servers were an important step in this direction, freeing us from comprehensive IDEs, but I think that's just the tip of the iceberg. More recently we have Tree Sitter, and GitHub at least can do some basic semantic analysis of code right in their UI. How can we make it even easier to build tools that understand a language? And what new interfaces can we put on top of them? What about a CLI that lets you grep code at an AST level, maybe showing the types of the expressions it yields? What about a declarative format like TextMate that lets you describe the basics of how a type system is wired, so you don't have to craft a whole language server by hand?
Maybe there's also something to be said for designing languages to be "tooling-friendly", even if that doesn't mean "as plain-text". Lowering the bar for writing tooling that can have a semantic understanding of the syntax and types, etc.
GitHub and VSCode both allow this (and I'm sure other rich editors do too). In fact, using GitLab at work, I open up most of the MRs I review in my local editor specifically for this reason
Yeah, sure Rich Editors support the "basics" of diffing, but for complex (i.e. 3-way) merges, I find them pretty limiting: stuff like Beyond Compare, p4diff are more useful in those situations IMO.
Yeah, github.com has started adding rich hover-overs and click-throughs on their web interface for some languages
I'm not familiar with those other tools, but it's not hard to imagine they or similar tools could one day add rich language integration using language servers, tree-sitter, or something else
The bigger problem is that neither rust-analyzer nor IntelliJ Rust currently support go-to-definition for trait methods. They will just give you a popup with all the overloads. I understand this is being worked on with Chalk but it's quite jarring coming from Java and C#.
I like to include traits closer to where they are used, to help with this. For example, “use cgmath::InnerSpace as _;” at the start of a small function. The “_” is a strong signal that I don’t need the type itself, but only properties of this trait.
I'll take almost any cost for extension traits. Going back to Python this is part of what I miss most.
There is no way to add methods to a type in Python that mypy understands. You have to create a new type. This leads to code that is very difficult to extend and pushes you towards inheritance.
For me, this became a huge problem when I was building a plugin system in Python. I wanted types to only own their own logic, but I also wanted types to be able to tell other types about themselves. For example, we have a Process and a File. I want to be able to go from Process to File with only one of those knowing about the other. Without extensions you are forced to create ugly workarounds.
Also, I've never had a problem with intellij and extension traits. I kind of wonder why you'd stick with a tool that's a bad experience.
I read your blog post and TBH I'm not really sure what extension types are, I've never used rust. But I tried this solution in python and am curious is this is something you tried?
>>> class A:
... def foo(self):
... print('foo')
...
>>> class B:
... def bar(self):
... print('bar')
...
>>> A.bar = B.bar
>>> A().bar()
bar
Functions in python are objects as well. So while you can't do this to built in python types, there is a high likelihood you can do this to most objects in python libraries.
The problem with your code is that it won't type check. It runs just fine because CPython doesn't care about type checking. But if you run mypy on it it will not understand.
rust-analyzer supports vim/emacs as well. in vim i can put my cursor over a symbol and get all the information id get from an ide via a quick shortcut. a feature-rich vimrc for rust can be seen here: https://github.com/jonhoo/configs/blob/master/editor/.config...
I wonder if the author is aware that rust has a well working, well searchable documentations you can easily open locally in case you have no internet ("rustup doc" & "cargo doc --open").
> You check the documentation of that crate and indeed, you find the write_u16 method there; phew.
Like, all wild card impl. traits are in the documentation, including such which are dereferenced.
EDIT:
Just to be clear if you pull in other code, including potential traits, you need to open the documentation specific to your crate (cargo doc).
So I've used Hack extensively and I'm sympathetic on the obscurity that can come from traits. It can get completely out of control. To be fair, object hierarchies, even interface hierarchies, can also get out of control, particularly when multiple inheritance is allowed.
But the real message here is if you're relying on regular expressions to implement IDE functions, plugins, language syntax checking, auto completion and so on, you're going to have a bad time. Period. This is the real weakness of VS Code IMO.
An IDE that operates on the syntax of the language is infinitely better than one that relies on regular expressions and simply treating source files as "text".
This is one reason I will always use Jetbrains IDEs given any choice because I know I can hover over a symbol and it'll tell me where it comes from. "Go to definition" will just work.
As soon as you start designing your language practices around the limitations of what regexes can do you're going to have an even worse time. For example, in Hack (@FB) we couldn't use namespaces or trait aliasing. You then had people asking "why are people creating all these abstract final classes with static methods in them?" when the answer is obvious: because they can't use namespaces).
My experience using C++ with VS Code was just horrible because the IDE was unreliable when it came to pointing out syntax or type errors. So you could waste your time compiling something that doesn't compile. I've never had this issue with CLion, for example.
Sorry, had to downvote this as I know people who have gotten burned by installing this plugin in the past and got confused by why their setup was so broken.
What you really want is to install the rust-analyzer plugin, not the Rust plugin.
I actually use rust-analyzer, but I’m not a hardcore Rust developer, and I wasn’t even aware there were two plugins. I picked the first result off of Google. The fact that the first result is a bad plugin seems like an issue with the Rust community.
> Microsoft is pushing their developed-in-house Rust plugin as the default.
I find this comment very confusing. Neither plugin was developed by Microsoft.
The "rust" extension generally comes up first because it's older and has significantly more downloads. That being said, it's true that imho everyone should use the new one.
I like the concept of making the 99% case as easy as possible and providing a failsafe when you encounter the 1% case, here an example.
People usually complain about Nim's import behaviour which is roughly an `import *` in other languages, making it hard to immediately tell where things come from. Personally, I find it fantastic because it removes a lot of boilerplate. In case of conflicts, the compiler throws an error, forcing you to qualify your import.
Now in Rust, you explicitly need to import the trait in order to use it because there _might_ be naming conflicts. Here, the 1% case spoils the 99% case.
I prefer the Rust/ES/Python etc. import syntax not because of ambiguity, but readability. Having everything traceable by quickly scanning the code, without IDE assistance, often speeds up my understanding of a file. And it becomes essential of you're reading code in an IDE-less environment, e.g. github gists. Worth the verbosity IMO.
Each language picks its own trade-offs. I’m glad Rust errs on the side of caution for imports. Unlike most other languages, with Rust I know I can enter any file and know exactly and precisely what symbols resolve to. This not only simplifies things for me but fits my mental model of programming much better (deterministic, predictable, explicit).
I feel like this is a pretty reasonable article, and it makes a good point. Much like Lisp most Rust codebases that I have seen are written usually by smaller teams of people. The issue the Author brings up is one that would be a problem if you had a very large team. Does anyone have any experience working on a large Rust project where there was extensive use of Traits?
I should probably write is_empty() more often, but, it is actually doing anything that len() == 0 isn't doing, or is it just that is_empty() better explains what I presumably wanted to ask?
I've not looked at the implementation, so I can't answer the first question, but to answer your second point/question: yes! I think getting across intent is very important for the other people reading your code.
I guess here it doesn't matter as much since it's a simple enough cases, but I'm just talking in the general sense.
With that said, I think is_empty probably has very slightly smaller overhead when it comes to figuring out the author's intent. Because, well, that was the intent. Length checking against 0 is merely a method of that intent.
Also, this "intent decoding" while reading code (no pun intended) adds up in mental cost, IMO. So, it wears down the the reader/reviewer attention more quickly.
> Some structures can answer .is_empty() much faster than calculating their length. So it is good to get into the habit of using .is_empty(), and having it is cheap. Besides, it makes the intent clearer than a manual comparison in some contexts.
It also has a lint for implementing both methods if you’ve only defined one, to help perpetuate the convention.
Thanks for the comment! I actually forgot to run clippy on these samples because they're so small; I'll fix up the code in the post to be more idiomatic.
I missed the intermediate work steps in your post when I initially wrote the response. That can be done a number of ways, e.g., breaking things up into to chained steps (each of which might use the above pattern) that mix validation and work and return a Result type with either the data for the next step to consume or an error.
Why are Rust developers so pedantic with this stuff.
What the author wrote is perfectly valid, fine code. Please stop insisting everyone does something one way or another when there's no clear advantage here to doing so except for the lack of single bloody keyword.
It's "perfectly valid, fine" with the exception that it's different from how the vast majority of Rust programmers write code. That in itself is a problem. Rust has two widely-used tools that enforce style: `rustfmt` and `clippy`. The former enforces formatting style and the latter general programming style, such as whether or not to use a `return` statement (`clippy` also does non-style lints).
For many of these formatting/stylistic lints, there are valid arguments to be made for an alternative choice. I personally often disagree with one or both tools. I still follow them.
The huge benefit here is that most Rust codebases look identical. I can open up burntsushi's regex crate and see code that is formatted and styled exactly like mine. It makes reading new code a lot easier.
> Who in their right mind has the courage to tackle a Java project without an IDE?
/me raising hand
I've used all the major IDEs on real projects for years at a time each, and so done years (often on the same projects) with just a quality editor and a set of external tools, and I honestly slightly prefer the latter.
I'm effective both ways, but my observation is that my work is of better quality when working in a non-IDE environment.
I fully understand that, as with most such things, says as much or more about me than about IDEs.
I think a large element is that, not having auto-complete, I find myself reading more docs (increasing system familiarity) and noticing unergonomic interfaces sooner.
The one feature I do miss is language aware navigation, but that loss is somewhat balanced by the lack of disruption when said navigation inevitably breaks, due to non-compiling code or working with new language features not yet supported by the tools.
* Vim or VSCode
- essentially zero plugins in both cases
- format on save using google-java-format
- syntax highlighting turned off in Vim, minimized in VSCode
- code folding disabled
- quickfix mode in vim and error display/navigatin in VSCode set up
* maven, gradle, or ant to build
* browser open to JavaDocs for stdlib and any library I happen to be
using (though I work very hard to minimize dependencies)
* JMC, VisualVM, JitWatch, async-profiler, etc. for profiling
I do occasionally still fire up IDEA to use the debugger or other tools, but rarely.
As for renaming methods/variables, for the latter, scope will almost always be small, so rename is usually trivial. For methods, it's almost always easy enough to rename, rebuild, then fix the errors.
I also typically use Vim for C programing, though there I often use cscope and/or ctags to somewhat smooth out navigation.
Again, I do not have a problem with good IDEs, and still occasionally use them. I just don't feel a need for them, and often prefer working in a plain editor.
I strongly agree with this as someone who enjoys writing Rust and who doesn't use an IDE (or an LSP plugin, or anything like that): it's mildly annoying to have to `use` a bunch of traits to bring their contracts into scope, but for that `use` to not be explicit about the contents of those contracts. Forgetting to import `Read`, `Write`, `TryFrom`, etc. probably accounts for over 75% of my "small" compilation errors.
I can't find it now, but I remember an RFC that proposes a solution to this, although in the opposite direction (i.e., more traits in the standard prelude). It would be interesting to see a `use` variant in a future edition of the language that allows users to bring in specific parts of the trait's contract, e.g. `use std::io::Read::read_exact`.
One possibility could be something like C# global usings added last year. Instead of having your usings in every file, you can define them with the `global` keyword and take effect in every file in your project. It's great for common things like the `IEnumerable` extension methods which reside in `System.Linq`.
The problem with non-greppable code isn't that "you should use an IDE". I use rust-analyzer and I dislike non-greppable code.
One concrete issue I ran into is that when cleaning up a list of imports, I can't use Ctrl+F to find out which ones are unused, since I can accidentally remove a trait which is never used by name but only imported for its methods. And I can't count on rust-analyzer's unused import detection working 100% of the time. And it's bad that some methods (for example the infamous `Read` or two `Write` traits) are inaccessible unless you add a trait import (which rust-analyzer sometimes knows how to add when I press Ctrl+.).
To me, the overarching value of Rust is locality of reasoning, and clearly indicating nonlocal reasoning in explicitly-readable and machine-checkable ways (eg. the `Cell` family of types). Trait methods are nonlocal reasoning, and they aren't clearly demarcated (a trait import behaves as`use module::Trait::*` but isn't written that way in the source code). If you're opposed to wildcard module name imports, you should be opposed to wildcard trait method imports.
> Who in their right mind has the courage to tackle a Java project without an IDE?
onestly, as an old-jeezer, I still don't understand the hate on IDEs.
Who in their right mind has the courage to tackle ANY project without an IDE?
I constantly see my collegues insist to use vscode without plugins, then fork or COMMANDLINE GIT (ugh..) then external tool for viewing jsons, and explicitly use postman and whatever tool...
and then fail to understand the very basic fact that an integrated collections of tools works better for the end user.
I tried and managed to avoid IDEs for years. Then I started to use rust for work. I accepted that I just had to use an IDE and I picked one. I was happy with CLion; it has a very good support for rust.
Unfortunately I had to develop on a remote machine (because compiling rust in my local machine is too slow). Jetbrains solution for remote development just didn't work for my use case (the remote was a M1 ARM running MacOS, not supported by jetbrains gateway). So I switched to vscode. Vscode editor is great, the support for remote development is also very good. Unfortunately rust-analyzer is not as good as jetbrains's rust plugin.
This is a problem with IDEs: you're either all in or you're out. Language servers did help decoupling language support from the actual window where you type stuff, but still not everything is in a language server (e.g. jetbrains rust plugin is not a reusable language server)
Yeah... I really feel for new developers... On many codebases they're going to have a bad time without an IDE to navigate all the abstractions. And then they're going to have a bad time trying to get by when microservices and the cloud break their IDE workflow and they can't fall back on a decade of using tools like vim, ssh, tmux. By the time IDEs are rewritten to cope with all the productivity obstacles containtainerization introduced microservices will probably be back out if style.
Personally I've just have bad experiences with integrated tools like that.
I'm sure if you just use it in some "normal" way it's fine, but I don't know how that is, and I've spent many hours wrestling with visual studio because the magic "easy to use" functionality stopped working or broke something. Add to that the interface changing with different versions.
Having several tools that do one thing that I understand has led to less frustration for me.
As a big proponent of grepability, I think extension traits are still reasonable. You need to find out what the trait is that defines that method, but that often isn't too difficult. Once you know that, you just need to look at the module defining the struct and the module defining the trait.
Two things that can improve this are preferring Trait::method(obj) notation and importing traits explicitly rather than relying on crate preludes.
Having magic names show up in my code without an explicit import somewhere can be really frustrating, especially in unfamiliar code bases where I'm trying to contribute a small change.
I think your suggestions do a good job addressing that; could be a thing for a style guide.
> Apparently, not all tooling has access to sophisticated language servers; for example, as far as I can tell GitHub source analysis won't be able to find where write_u16 is coming from, and the same is true of Sourcegraph.
FYI, Sourcegraph supports LSIF generated by rust-analyzer. For example, https://sourcegraph.com/github.com/matrix-org/matrix-rust-sd... shows lock method belongs to tokio. It should be able to show origin of write_u16 as well (In the precise mode, not search based).
Gitlab can also consume LSIF, and github should do it someday, IMO, their current way doesn't scale. In most languages you should implement a full compiler front end in order to provide a 100% correct goto definition.
Depending on the day I will either dream of a language specifically designed with a particular IDE in mind and all the cool stuff you could do with that as a ground up assumption (google f# type providers for some inspiration), other days I dream of a language whose design specifically does not require an IDE, so you can easily understand the code from github or vim or anywhere. For instance global or function wide type inference is handy but can make non IDE use of a language really hard because you can’t immediately see what the types are.
Perhaps the general design principle should be to tend towards one extreme or the other, rather than end up with a language that needs an IDE but doesn’t leverage it fully.
I've been digging more into Rust, and things not working because I forgot to import the trait drives me bonkers. Like, you create a struct, you build an implementation, you implement a trait on it, you try and use it in another module, you can't, you feel like you must have goofed something, you hack around for an hour, oh you remember you forgot to import the trait you already implemented, you open up HN in a fury.... Is there a really important reason rustc doesn't know what traits are implemented on what structs? It can't be a dependency graph thing, it has to know about the struct in the first place.
I suggest you start reading the compiler error messages as they say exactly what trait is missing, no need to waste an hour.
For example for this code:
trait Foo {
fn foo(&self) {}
}
struct Bar;
impl Foo for Bar {}
mod test {
use super::Bar;
fn test() {
Bar.foo();
}
}
22 | Bar.foo();
| ^^^ method not found in `Bar`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
20 | use crate::Foo;
|
A less condescending way to say what you said is, "oh they're pretty clear in the compiler output, are they buried or something?" And then I would say that in the linter in Vim, you only get the "method not found in Bar" which could be all kinds of things.
This seems like you’re using a relatively uncommon tooling setup (no language server, some kind of inline vim errors rather than the command line), and expecting that to “just work.” As a user of arcane tools, it seems to me a slightly unreasonable expectation. If you’re not going to go down the “golden path” of using rust—analyzer, you’ll need to assume that your editor’s support for the language may be incomplete out of the gate and figure out how to supplement it. Even with rust-analyzer, the source of truth is always `cargo check` and friends, so I recommend getting super familiar with them on the command line, regardless of your editor setup.
Yeah no you're definitely right, I'm definitely not in the golden path for Rust dev. I guess my quibbles (which I would cop to being quibbles... I think anyway) are:
- I'm already using 'cargo check' as my linter (rustc doesn't understand modules, seemingly).
- My setup works for JavaScript/React/Svelte, SQL, C/C++, Python, and Go.
- It can't be the case that (Neo)Vim + Ale [0] is arcane. Also the link in "Tools" from rust-lang.org goes to the rust.vim repo, which defaults to Syntastic, which I'm pretty sure has the same issue.
I don't necessarily think I should extrapolate from my experience to a broader "Rust is different for little/no gain" critique I've made in the past, but I sure want to haha. And to kind of anticipate a little, I wouldn't blame Ale here because--again--it works for everything else.
But anyway, yeah I've mostly given up on linting and I just run 'cargo check' in a terminal buffer now. It's probably something as simple as adding a command-line argument to... idk cargo or rustc to make the additional "help" format a little more amenable to Ale? Maybe I'll take a crack at it when I get some time.
cargo/rustc can already provide all its output as structured json, including the appropriate byte position to apply substitutions suggested in help blocks.
I'm pretty sure that's already how it works yeah, then yeah maybe adding some smarts to ALE is the right thing to do here? Unclear without looking into it more.
I haven’t used ALE in a while, and my colleague who uses vim uses CoC, but as far as I understand, rust-analyzer should “just work” with ALE. You may need to install rust-analyzer (via cargo) and tell ALE to use it, potentially? There’s an older and not well maintained language server called RLS. I would be surprised if ALE defaults to that one, but that could be part of why you aren’t getting great tooling out of the box if so.
I did find this blog post, which seems to suggest there may be a bit of extra config required to get rust-analyzer going with ALE: https://petermalmgren.com/rc-batch-day-9/
Yeah I think maybe I'll just come around to rust-analyzer? My experiences with language servers has been really spotty: they lose the state, eat CPU, want to autocomplete everything, etc. But idk, if it's a "this is the way" thing, I guess part of learning is adopting the customs. Just feels a little disappointing I can get by with like goimports/gofmt/go vet in Go, but there's no analog in Rust.
Oh no, the nice thing about it is you can basically configure it to do whatever you want. You can choose linters or fixers, lint/fix on save or on a configured "I've left insert mode" lag, etc. etc. It's actually really amazing [0].
One of the things I've really enjoyed about the Free Software ecosystem is choice, like mostly before Rails the attitude was "we don't presume to know the best anything, here are some options." I'm not saying there aren't tradeoffs (complexity, bitrot, 7 half-baked options vs. 1 incredible one), only that I kind of low-key resent the "opinionated" software that's out there. I guess I don't really see it as opinionated (like I think Rails' convention over configuration was actually a pretty good idea and exemplifies the "execute" phase of the explore/execute cycle of tooling), I see it as "flashy and targets the 80% use-case to get mindshare".
To be clear, I don't think rust-analyzer is that opinionated or flashy or lazy. I think it's awesome. But Rust's linting story is essentially "why would you use anything besides rust-analyzer, language servers are awesome and have no downsides." Which like, yeah they do.
Oh cool I didn’t know that ALE was so flexible, thanks!
And yeah rust-analyzer is definitely the happy path, but if you were trying to get by without a language server (not going to lie on a very large rust project rust-analyzer’s RAM usage can get up there), I think that `cargo check` output properly parsed and displayed in your linter would probably get you a huge portion of what you need. Between that and rustfmt, I think the only thing you’d be missing would be go to definition and that kind of thing (but you can set that up with a ctags-style thing or just replace with fuzzy grep).
In fact, before I figured out how to get rust-analyzer working in emacs, I used a setup where `cargo check` ran on save, and the output showed up in flycheck. From what you said about ALE, it sounds like you could set it up the same way. The main thing would be making sure there’s a way to get the FULL output of `cargo check`, probably not inline because there tends to be a lot of it. But e.g. with emacs I get error underlines that show the first part of the error, and then I can expand that in a popup window, or run cargo check, which opens cargo check in another buffer, with the output parsed so that if I press enter on an error, it brings me to the location in the code.
All of which is to say that it is doable! Maybe it would just take some extra ALE configuration, since the defaults are probably oriented towards people who are using rust-analyzer.
If you’re using rust-analyzer in your editor of choice, it will often suggest and auto-import traits when you try to use their methods with the trait not in scope. It knows, based on the dependency graph and your own code, all of the traits that could provide a method.
I use rust-analyzer in emacs, but it’s available in any editor that supports language servers.
With my setup, I’ll start typing a method, get a completion for the trait method I want with the trait indicated, and select the completion. The trait is then automatically imported and the method is completed.
rust-analyzer is a language server, which (along with autocomplete and maybe infuriatingly) I'm not into. I think this mostly reinforces Eli's point, which is broadly that you need a lot of editor support to use Rust reasonably ergonomically.
I think the sibling comment pointing out that the compiler errors tell you exactly what trait you need is for you then! You can get them with just a `cargo check`, no language server required.
Haskell automatically imports all typeclass definitions (equivalent to traits) when you import a module, so you sometimes see lines like
`import Foo.Bar ()`
Which looks like it's just importing nothing at all (`()`) but is actually pulling in some traits and nothing else.
You don't see this often because "orphan instances" (instances defined in a file besides the location of the trait definition or the type definition) are discouraged, but they do occur.
I had also thought that there was a reason one could need that pattern to import instances even in the absence of orphans, but on reflection I think I was mistaken.
This is also the thing I don't like in Go: sometimes it's really hard to find what interface a type implements, why a type implements (or not) a certain interface and where the f the implementations are.
You would still need to import the malicious trait in order for it to be extended. If you already have included a bad crate which has a bad trait, then why not replace the implementation of a well-named function. I can’t see a reason to use a homoglyph in the function name.
Remember that in Python you can mutate `globals` to change the meaning of arbitrary symbols. It seems like a lost cause if you are running a compromised library.
I think I’ve unconsciously biased myself away from extension traits and have favored using traits at function boundaries instead. This still keeps the greppability and the polymorphism. But sometimes can make lifetimes trickier.
I have been bit by the lack of greppability for traits a few times. I love the trait system, but I wish there was a way to track back to definitions without relying on the IDE of the day. It reminds me of old C++ nightmare scenarios where the original developers would overload all the operators and bury all sorts of redefinitions in an endless hierarchy of includes.
issue `cargo doc` command to generate the documentation that will expose extension traits among other things. Not perfect, certainly. But together with rust-analizer and grep the generated docs cover all the bases.
> [...]
> It's entirely possible that using a language like Rust without a sophisticated IDE is madness, and I'm somewhat stuck in the past. But I have to say, I do lament the loss of greppability.
Seems more like Eli has completely missed an important and integral feature of the rust ecosystem: `cargo doc`.
While the stdlib sadly remains out of it (long-standing RFC 2324[0]), cargo doc will otherwise generate the documentation including all dependencies by default.
This means you can in fact get this information by "checking the documentation", just the right one: a global search in the project's `cargo doc` will quickly tell you where the method comes from[1], without the need for any IDE or complicated integration (though I don't think it works with text browsers as it uses javascript, for terminal / console contexts maybe try `rusty-man`, I don't know how good it is tho).
I couldn't imagine working on a Rust project without having its `cargo doc` permanently open in a tab, having the unified offline documentation for all your dependencies is just way too useful, I run `cargo doc` almost as often as I run `cargo check` (despite that being mostly useless for binary crates...)
[0] https://github.com/rust-lang/rfcs/issues/2324
[1] just tried it on a local project, the first methods it surfaces are 4 methods from byteorder (on ByteOrder, WriteBytesEx, BigEndian, and LittleEndian) and one from serde_json (on Formatter).