I come from a Java background and have recently started with Rust.
The official Rust doc is pretty self-explanatory except the chapter that explains Crates and Packages.
The official doc complicates it with so many ORs and ANDs while explaining the two.
This reddit post explains it a little better, but is not thorough.
What is the exact difference between a Crate and Package in Rust? Where/When do we use them?
Much thanks!
Crates
From the perspective of the Rust compiler, "crate" is the name of the compilation unit. A crate consists of an hierarchy of modules in one or multiple files. This is in contrast to most "traditional" compiled languages like Java, C or C++, where the compilation unit is a single file.
From the perspective of an user, this definition isn't really helpful. Indeed, in most cases, you will need to distinguish between two types of crates:
binary crates can be compiled to executables by the Rust compiler. For example, Cargo, the Rust package manager, is a binary crate translated by the Rust compiler to the executable that you use to manage your project.
library crates are what you'd simply call libraries in other languages. A binary crate can depend on library crates to use functionality supplied by the libraries.
Packages
The concept of packages does not originate in the Rust compiler, but in Cargo, the Rust package manager. At least for simple projects, a package is also what you will check into version control.
A package consists of one or multiple crates, but no more than one library crate.
Creating packages
to create a new package consisting of one binary crate, you can run cargo new
to create a new package consisting of one library crate, you can run cargo new --lib
to create a package consisting of a library as well as one or multiple binaries, you can run either cargo new or cargo new --lib and then modify the package directory structure to add the other crate
When should you use crates, and when should you use packages?
As you can see now, this question doesn't really make sense – you should and must always use both. A package can't exist without at least one crate, and a crate is (at least if you are using Cargo) always part of a package.
Therefore, a better question is this:
When should you put multiple crates into one package?
There are multiple reasons to have more than one crate in a package. For example:
If you have a binary crate, it is idiomatic to have the "business logic" in a library in the same package. This has multiple advantages:
Libraries can be integration tested while binaries can't
If you later decide that the business logic needs to also be used in another binary, it is trivial to add this second binary to the package and also use the library
If you have a library crate that generates some files (a database engine or something like that), you may want to have a helper binary to inspect those files
Note that if you have a very big project, you may instead want to use the workspace feature of Cargo in these cases.
Related
I am making a library in rust, and I learned that I could put example usages of the library in an examples directory in the root directory and then run them with cargo run --example hello. However, I noticed that the dependencies I specify in Cargo.toml are also available in the example code. This is a bit odd to me. These dependencies belong to the library and the user shouldn't be aware of them and certainly not use them himself (unless he happens to depend on them too, I guess).
For example, I have nalgebra = "0.31.0" in Cargo.toml and use it as a backend for vectors and matrices and other mathematical operations.
I created my own wrappers for vectors and matrices, so the user shouldn't access nalgebra types directly. I find it weird that when writing examples for the library, I can write use nalgebra as na; and use it without any issues. I guess it's helpful for debugging purposes, but when writing a complete example, this code shouldn't compile.
Is there a way to generate compiler errors when trying to use dependencies in example code, or is it simply my responsibility to write examples that reflect actual usage of the library?
is it simply my responsibility to write examples that reflect actual usage of the library?
Yes.
This comes up in other situations too: as long as you're writing code inside a single Cargo package, all that code shares a common set of dependencies (with the exception that [dev-dependencies] are not available to lib and bin targets, only test, example, and bench targets).
If you want a different set of dependencies, the only option is to split the example(s) into a separate package (which can be in a workspace with the library package). In my experience, it's somewhat common for Rust projects to have a separate package for examples, particularly when those examples have shared code; for example, a graphics library whose examples need window-management code might have structure like
Cargo.toml # workspace declaration
the-library/
Cargo.toml
src/
lib.rs
examples/
Cargo.toml # has a `{ path = "../the-library" }` dependency
src/
lib.rs # contains setup code the examples use
examples/
ex1.rs
ex2.rs
There is a disadvantage to doing this: your examples won't be included in the package uploaded to the registry, and the new rustdoc feature that shows snippets of examples in the docs of functions that they use, won't find these examples.
However, if your only concern is “the examples shouldn't refer to this library”, then probably the best option is to take care writing the examples.
You can also use clippy to check for unwanted mentions of functions or types, with the disallowed_methods and disallowed_types lints. However, the lists of disallowed items are package-wide configuration, so you'd have to specifically disable the lint inside your library so that clippy doesn't warn on those uses of the dependency.
I am new to Rust, so excuse me if im just doing things horribly wrong.
While larning the language i wanted to try out different bindings of libraries that i already used in other languages, amongst them SDL2, SFML2, Gtk3.
To my surprise, nothing seemed to work out of the box. They all depend on C libraries and those don't come with the cargo crate. I managed to get SFML2 to work after following the readme and manually copying .lib and .dll files to the right places. I tried to make the Rust linker to look into my vcpk directory for .lib files, sadly with no success.
The whole point of a package manager kind of is to automate these things for you. Other package managers like NuGet for C# dont require you to manually fiddle the dependencies for their packages together.
Getting rid of the thirdparty library management hell of C/C++ was one of the reasons why i took a closer look at Rust.
Am i doing something wrong, or is this just how things are with Rust/Cargo?
Cargo is a build management and source package management tool for Rust code - it is not a tool for managing binaries or compiling other languages such as C or C++.
Having said that, it is a very flexible tool so it is possible for crates that provide bindings to libraries written in other languages to "bundle" the libraries they depend on.
The Rust-SDL2 crate, for example, does offer such a feature - as it says in their README:
Since 0.31, this crate supports a feature named "bundled" which
downloads SDL2 from source, compiles it and links it automatically.
To use this, you would would add it to your Cargo.toml like this:
[dependancies]
sdl2 = { version = "0.34.0", features=["bundled"] }
Not all such binding crates support bundling, especially if the libraries they bind to are large, complex, have lots of their own dependencies and/or have lots of compile time configuration options.
In those cases you will either need to install a pre-compiled binary, or compile them from source yourself.
According to its manual, Cargo packages can have multiple executable targets, but only one library target is allowed.
A package can contain zero or one library crates and as many binary crates as you’d like. There must be at least one crate (either a library or a binary) in a package.
Why is it limited to one? What are the reasons and benefits?
Cargo is primarily a package manager. Thus, the primary role of a package is to define a library.
When we use a crate as a dependency, we only specify the package name in our Cargo.toml. Since there can be at most one library, Cargo doesn't need you to specify which one to use. If it were allowed to define multiple libraries in the same package, then we'd need to specify a way to define dependencies between them, so we'd have two ways to declare dependencies (external packages vs. internal crates), making the system more complex.
On the other hand, adding a dependency that doesn't provide a library doesn't make sense, at least not with Cargo, since Cargo only cares about the library target in that context. Thus, there is no reason to limit the other types of targets (binaries, examples, tests, etc.) to one each.
I would expect that a cargo package can only have one library target because a library crate is by definition a collection of items (functions, types, traits, macros, values, etc.) while a binary crate has only one externally visible thing, a main entry point. Consequently, while the library crate's name is merely the root module within an hierarchy, the binary crate's name is the only thing.
By forking and playing with some code, I noticed that Cargo can download and bundle multiple versions of the same crate in the same project (native-tls 0.1.5 and 0.2.1, for example). I have wasted so much time by looking at the documentation of the wrong version.
I have looked for some information about this behaviour but I was not able to find anything. Is this documented somewhere?
Is there an easy way to determine/detect the version used by the code you're working on (current edited file)? Or can we tell Cargo to show some warnings/prevent the build if two versions the same crate are required?
It was a conscious decision when designing Rust to allow multiple versions of the same crate.
You have probably heard of Dependency Hell before, which occurs when 2 (or more) dependencies A and B have a common dependency C but each require a version which is incompatible with the other.
Rust was designed to ensure that this would not be an issue.
In general, cargo will attempt to find a common version which satisfies all requirements. As long as crate authors use SemVer correctly, and the requirements give enough leeway, a single version of the dependency can be computed and used successfully.
Sometimes, however, multiple versions of the same dependency are necessary, such as in your case since 0.1.x and 0.2.x are considered two different major versions. In this case, Rust has two features to allow the two versions to be used within the same binary:
a unique hash per version is appended to each symbol.
the type system considers the same type Foo from two versions of C to be different types.
There is a limitation, of course. If a function of A returns an instance of C::Foo and you attempt to pass it to a function of B, the compiler will refuse it (it considers the two types to be different). This is an intractable problem1.
Anytime the dependency on C is internal, or the use of C is isolated, it otherwise works automatically. As your experience shows, it is so seamless that the user may not even realize it is happening.
1 See the dtolnay trick that crate authors can use to allow some types to be interchangeable.
Cargo can indeed link multiple versions of some crate, but only one of those versions can be a direct dependency. The others are indirect references.
The direct reference is always the version referenced by Cargo.toml and on top-level of Cargo.lock (while the indirect references are in the dependencies subsections).
I am not sure how much it is documented, unfortunately.
This question was asked before Rust officially supported incremental compilation. Rust 1.24.0 and later enable incremental compilation by default for development (debug) builds.
I'm an outsider trying to see if Rust is appropriate for my projects.
I've read that Rust lacks incremental compilation (beta features notwithstanding).
Is this similar to having everything be implemented in the headers in C++ (like in much of Boost)?
If the above is correct, does this limit Rust to rather small projects with small dependencies? (If, say, Qt or KDE were header-only libraries, then programs using them would be extremely painful to develop, since you'd effectively recompile Qt/KDE every time you want to compile your own code.)
In C and C++, a compilation unit is usually a source file and all the header files it transitively includes. An application or library is usually comprised of multiple compilation units that are linked together. An application or library can additionally be linked with other libraries. This means that changing a source file requires recompiling that source file only and then relinking, changing an external library only requires relinking, but changing a header file (whether it's part of the project or external; the compiler can't tell the difference) requires recompiling all source files that use it and then relinking.
In Rust, the crate is the compilation unit. (A crate can be an application or a library.) Rust doesn't use header files; instead, the equivalent information is stored as metadata in the compiled crates (which is faster to parse, and has the same effect as precompiled headers in C/C++). A crate can additionally be linked with other crates. This means that changing any of the source files for a crate requires recompiling the whole crate, and changing a crate requires recompiling all crates that depend on it (currently, this means recompiling from source, even if the API happens to not have changed).
To answer your questions, no, Rust doesn't recompile all dependencies every time you recompile your project; quite the opposite in fact.
Incremental compilation in Rust is about reusing the work done in previous compilations of a crate to speed up compilation times. For example, if you change a module and it doesn't affect the other modules, the compiler would be able to reuse the data that was generated when the other modules were compiled last time. The lack of incremental compilation is usually only a problem with large or complex crates (e.g. those who make heavy use of macros).