My reluctance about using Cargo


#1

While I will admit Cargo has its uses and seems like a very handy tool, I have some irks about it I feel that might be solvable through some assistance. Before I picked up Rust, with C/C++/C#/Java/etc, if I wrote a piece of code I wanted to try out that was part of a bigger project, often a binary and not a library, I’d simply make a small program that uses it in its main() function and then build it with the CLI (ie. g++ test.cpp mycode.cpp -o test)

However, with Rust, Cargo seems to insist on a directory layout that, in my opinion, is overkill. Additionally, I find myself having to create entirely separate Cargo projects just for these tests because otherwise it either tends to whine about my setup, or it ignores certain code.

I have a hunch that Cargo has the customisation I’m looking for, but I can’t seem to find anything on Google about it. I did try Go in the past, but the package ecosystem really made me cringe. With Rust and Cargo, I see that while there’s a similar ecosystem, there are options for allowing otherwise and I’m thankful for that.


#2

For small pieces of test code, you can run rustc test.rs to produce a binary named test (or test.exe).


#3

I know that much, but what if they require dependencies other than my code? I found myself using the time crate, for example.


#4

You can use dev-dependencies to specify dependencies that will be used for tests and examples but not for the crate itself.


#5

You can keep all of your “small” binaries inside one project in a src/bin directory. Then you can run foo.rs with cargo run --bin foo.

Speaking of directory layout, you can adjust the Cargo.toml not to use src directory. I think the syntax for that is

[[bin]]
name = "foo"
path = "main.rs"

If you really want to use rustc with dependencies, it’s certainly possible. You can learn how to do it by passing --verbose to cargo. You can also include the whole directory with dependencies with -L flag. You can use a separate cargo project that just specifies some libraries as dependecies (let’s say you name it libs), and then use:

rustc -L libs/target/release/deps foo.rs

Note that you’ll need to rebuild libs after each Rust update.

(I thought that you can put this directory in LIBRARY_PATH env variable to make this -L flag persistent, but it doesn’t seem to work)


#6

I see. Not that this is a big inconvenience, but are main.rs and lib.rs required filenames?


#7

Not really. They are just defaults (lib.rs for libraries, main.rs and bin/foo.rs for binaries). You can rename it to what you want. See this section of cargo manifest description.


#8

Not liking Cargo is hard.


#9

Yeah. I’ve looked into Meson but not much luck there.


#10

You can run quick one-off scripts without Cargo.toml using https://github.com/DanielKeep/cargo-script

I have a hunch that Cargo has the customisation I’m looking for, but I can’t seem to find anything on Google about it.

Even if you could, it would be better to stick to the conventions because it means fewer surprises for other users.


#11

Putting Cargo.toml and lib.rs in the same folder is also a common practice for small crates. See here for example. Look at their Cargo.toml to see how they did it.


#12

I see! And for dependencies I’ve written myself, it would be path = ".."?


#13

.. seems like a bad idea if it goes outside your repository. Maybe use gitmodules to refer to code outside your repository?


#14

FWIW for this sort of thing I sometimes create a rust file inside the tests directory and use that to try things out. It uses the dev-dependecies as specified in Cargo.toml and obviously the rest of the project…

Not sure if that really fits your needs, just wanted to note the possibility here…


#15

I sympathize with the OP, when it comes to testing small snippets of code with external dependencies. I’ve been working on a little runner which compiles snippets with -C prefer-dynamic, and keeps a cache of dependencies compiled as dylibs (also with dynamic linking since you really don’t want two copies of the stdlib in a program). It is significantly faster to compile/run code like this, since the linker doesn’t have to do so much work stitching a static executable together.

Unfortunately, only works with simple dependencies without transitive dependencies. For instance, you can compile regex as a dylib, but using the necessary prefer-dynamic means that the dependencies aren’t linked in statically. Anyone know of a way around this?


#16

:+1:

I write throwaway Python scripts all the time, but not Rust scripts - partly out of habit, partly because of compile time or verbosity, but to a nontrivial extent just because of the bureaucracy of Cargo’s directory layout. Hadn’t heard of cargo-script; I might use it, but I’d like to see it as an official (built-in) feature.


#17

Why would I need gitmodules for my own code that I’m just making a mini test for?


#18

If you want something where you can just quickly test out code, then Rust playground is a lifesaver: https://play.rust-lang.org/


#19

I think krdln’s suggestion is better, since online code editing is a pain.

So I created a little binary project ‘cache’ and added the json and time crates as dependencies. Can build this as usual.

Then I can compile and run any little program needing these crates with

rustc -L ~/rust/test/cache/target/debug/deps test.rs && ./test

(Obviously a good candidate for a little shell script)

Plus , in ~/rust/test/cache, I can run ‘cargo doc’ and open the json docs as ~/rust/test/cache/doc/json/index.html, etc.

So keep everything useful in cache and compile & test happily.


#20

Does the playground import crates?