I'm relatively new to Rust, therefore I'm still learning. I started form isolating a sharebale code in crates. But since a crate gets recompiled at any its code change, I started simply using macro include! . Do I do right, or veterans of Rut programing recommend using external crates over?
A crate is a single unit of compilation. If you include all your dependencies source code with include!, that single unit of compilation has to do it all. Whereas if you split your code into multiple crates, compilation of them can be cached and parallelized by the compiler/build system. So yes, extern crate is much, much more efficient than including the source code with include!.
include!() has no caching. It's a very naive copying of code, with nearly all the flaws and costs of C's #include (only improvement is that it's AST based, not naive text concat).
It's strictly worse than extern crate. The worst case for extern crate is that MIR will be monomorphised and recompiled, adding compile cost to the crates using it.
include!() always pays the cost of the worst case of extern crate and also add cost of reparsing, macro expansion and high-level analysis. When a crate using include!() is rebuit, it will rebuild that code every time instead of using a cached rlib.
Also note that include! won't work correctly across crates. Dependencies will be missing, nominal types will have wrong identity, orphan rules will apply to a wrong context. It's not even a real alternative to extern crate, and will cause chaos if you try to use it as such.
It makes sense. But what about such use case:
ver.rs
fn ver() {
println!("Version of the {product} is {VER}")
}
and main.rs
const product: &str = "ReadYou";
const VER: &str = "1.01:126";
include!("ver.rs");
fn main() -> Result<(), Box<dyn std::error::Error>> {
ver();
}
Is such approach reasonable when an included code has some references to a wrapper elements?
No, it's not reasonable. Just use the module system the way it was meant to be used:
ver.rs:
use crate::{product, VER};
fn ver() {
println!("Version of the {product} is {VER}")
}
main.rs:
mod ver;
use crate::ver::ver;
const product: &str = "ReadYou";
const VER: &str = "1.01:126";
fn main() -> Result<(), Box<dyn std::error::Error>> {
ver();
}
Splitting up crates so less code needs to be recompiled is a skill to be sure. But it's necessarily no better and possibly significantly worse using include! with a good build system.
IIRC you're rolling your own, you might want to look into pipelined builds
Think of crates as something you pull out of code you're working on so they don't need to be rebuilt, rather than for the video you're working on and you're halfway there, though if you can develop against tests local to a crate (technically package in cargo terms) you can really speed up your inner loop that way too.
Perfect, I have heard enough arguments against using insert!. I think insert_str! still has a value. But since include! was introduced, it should be some cases when using it makes a sense.
Yep! One important difference for include! is that it places the code directly into the location it's used, even inside a method
But that quirk isn't by itself too useful, since modules and depending on inlining are generally a better experience and just as flexible.
The bigger and more important use is to pull code-gen into the crate: for example this is the recommended setup for bindgen
This setup is preferred over generating to src not only because it's easier to exclude the committed code but because cargo can then separate the temporary directory between concurrent builds and lock it to avoid racing build scripts.
But overall, it's something of a niche feature, given how flexible the module system is, e.g. conditional includes can be emulated with:
#[cfg(windows)]
#[path = "foo_windows.rs")]
mod foo;
#[cfg(unix)]
#[path = "foo_unix.rs")]
mod foo;
It looks useful and I added this in one of my open source projects.
Generally include! is not recommended, it directly pastes raw code and is not very performant. extern crate is getting replaced by Cargo.toml and use my_crate. You could simply link a local crate as a dependency(and a member, if you're using workspaces) and Cargo would handle it automatically.
An exception where include! is still used a lot is when you're working in a build.rs script
Thanks for the feedback. I'm just trying all features of Rust regardless how they are valuable for a real use. As for now, I found (recommended here) #[path = "../../simincmod/real_path_win.rs"] quite cool, because I can keep a main source relatively small escaping platform specific modules in includes which I can share between projects. Sure, I can use crates for the same purpose, however includes look cooler for me.
I mean, the rust docs are pretty clear on this
Warning: For multi-file Rust projects, the include! macro is probably not what you are looking for. Usually, multi-file Rust projects use modules. Multi-file projects and modules are explained in the Rust-by-Example book here and the module system is explained in the Rust Book here.
and a bit later
The include! macro is primarily used for two purposes. It is used to include documentation that is written in a separate file and it is used to include build artifacts usually as a result from the build.rs script.
So you are probably just gonna make things harder for yourself in the long run.
It's a priceless quote. First, as I started using include!, I hit a situation when a compilation error happens in an included file, and the file is outside of the project tree, I can't open the file in my IDE. Okay, I modified IDE to be able to open it, but still I couldn't edit the file, because it isn't a part of the current project. I have to create other project, but IDE doesn't give a possibility transparently switch between projects. So using include! gives a lot pain. However, I disagree with:
because keeping doc in a separate file is a bad idea, since you need to keep both files in sync and it's hardly possible.
Regarding second use case, I already use it, and there I agree completely.
But only having one file to update is why it's suggested actually, though in the include_str! form, eg
#![doc = include_str!("../README.md")]
Will copy your repo readme into the crate documentation so it's shared. There's lots of tricks similar to this, but in general they're not overly useful if you can just link to a common section instead.
However, you can use them to include usage examples that you also use as inputs for tests (that don't fit the doctest model) or even just to get very lengthy examples out of line if you prefer that.
If you meant that having the documentation for eg. a method in a separate file doesn't make sense: yes, so don't do that ![]()
Why omitting reading documentation is pretty bad, because you will constantly invent the wheel. Here is a small snippet from my codebase:
pub fn get_help() -> String {
include_str!("../doc/help.txt").to_string()
}
Actually getting out of the sync is good here, because you may want to add certain features, but keep them hidden from a user some time.
I'm afraid I've now got no idea what you mean. "doc" in this context normally means rustdoc , not cli help text, so we've apparently been talking past each other there; you apparently complained earlier that using include means things get out of sync; you now show a (perfectly reasonable) use of include for help text, then refer to it as being "deliberately out of sync" (with what?).
Clearly you do know what it does, so I'm not particularly concerned, I think we've just been talking about different things.
You are absolutely right, one thing you only miss, it's human's laziness. It creates some gap in understanding. Anyway, I got a very useful information for myself, sorry if talking to me wasn't so fun for you.