My reluctance about using Cargo

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 Likes

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

1 Like

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

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

2 Likes

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 Likes

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

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.

2 Likes

Not liking Cargo is hard.

33 Likes

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

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 Likes

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.

1 Like

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

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

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…

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?

:+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.

3 Likes

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

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

4 Likes

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.

2 Likes

Does the playground import crates?