Rust 1.43
blog.rust-lang.org
blog.rust-lang.org
> This release is fairly minor. There are no new major features. We have some new stabilized APIs, some compiler performance improvements, and a small macro-related feature. See the detailed release notes to learn about other changes not covered by this post.
is an attempt by me to maybe address this. We've historically said similar-ish things, but I'm trying to be a bit more blunt about the magnitude of changes. Any feedback on this would be useful!
The consensus with error handling is that the limitations of std error trait have now been sufficiently fixed so as to be usable without anything more. But you can use a crate (like `thiserror`) to reduce boilerplate if you really want to.
You have to update your dependencies though because every now and then, changes lead to breaking builds. I have a codebase from 2015, last touched in 2016, and many dependencies failed to compile, some because of changes in the language (those changes are only done to fix bugs but they still cause churn to users), others because the openssl wrapper doesn't support newer natively installed versions of openssl. It took me roughly an hour to fix all the issues and it's only a few hundred lines.
Admittedly this is much much less effort than staying up with whatever the currently hyped error handling library is. And C++ projects often need effort to work on newer compilers as well because they invoke UB and the optimizer changed and led to segfaults. Seen it with my own eyes.
So I think that when in 2024 someone build a code base from 2020 this shouldn't be a problem.
Also sadly currently rust has not yet completely escaped from the C/C++ UB optimization hell ;-). There are some bugs caused by the interaction of Rust and LLVM UB optimizations. Like there was a problem with some float casts being UB (but should not have been).
But we are getting there.
And one of those is just about to get fixed, it looks like =]
My own code has compiled just fine except for a ton of warnings (most about try! -> ? change) and the needed API updates of dependencies (2 changed lines).
Most of the effort I spent on actually updating as little as possible. If you update everything you fix all issues resolvable by updates but you also have to adjust to every API change :).
Dependencies can set lints to deny. When lints change, they might not compile anymore.
(I think denying lints is a bad idea, just like -Werror)
The current error handling consensus seems to be "anyhow for applications, either no crate or thiserror for libraries." Obviously this is not uniform, but the discussion + download counts point to this, imho.
https://github.com/dtolnay/anyhow https://github.com/dtolnay/thiserror
In other words I think there needs to be a long term standardisation plan to keep the language stable, but for now this is a good step.
You should only need to update at your own pace or because your dependencies force you to, and they shouldn't do so often if they care about supporting as many users as possible.
Big changes, like async/await sugar or impl Trait or the ? operator are uncommon and they either make your code that much nicer to read or allow new constructs that were previously impossible.
We never make changes for the hell of it.
But also, all changes are purely additive at this point. The language is stable. It has been for years.
Using the Rust test framework, limiting direct memory management to the low number of unsafe functions I need to pass raw data/pointers across that FFI boundary, being able to count on the compiler to check if I'm doing something dumb, and being able to easily add other dependencies into that Rust .so/.a file product Cargo have all been huge productivity boosts compared to pulling out valgrind/gdb whenever I run into a segfault because I've done something stupid.
> You can now use associated constants on floats and integers directly, rather than having to import the module. That is, you can now write u32::MAX or f32::NAN with no use std::u32; or use std::f32;.
is neat! Awesome! Thanks!
https://github.com/SergioBenitez/Rocket/issues/19
Anyone have any background on what's holding back proc_macro_hygiene and why that's even a requirement of Rocket?
That said I’m not an expert on Rocket and don’t know what Sergio’s plan is exactly.
The last time I wrote something I just used raw hyper. I didn't need complex routing though, if I did, that would have been an absolute pain. It was also a read-only service so I didn't need auth or a lot of other stuff that's needed in every real app.
Cargo needs a lot more ways to be interrogated about the artifacts it produces and control over how they are produced.
The end result is this bypasses npm's downloading and managing of node_modules, does it all in a fully declarative fashion, and then hands control over to npm to build the package. Also, each dependency's source is cached independently by Nix, so if multiple packages use the exact same dependency, it doesn't have to be redownloaded (unlike Rust where the entire dependency tree is vendored separately per package).
To be clear, Cargo downloads the source globally. The compiled output is per-project, however.
I'd like to be able to create plugin-style cdylibs that get loaded into a host program. For instance, PostgreSQL extensions are shared libraries (typically written in C) that need to be loaded into a running instance of postgres for integration testing. I am trying to make it reasonable to write such extensions in rust (https://github.com/jeff-davis/postgres-extension.rs).
The environment variable to find the shared library would solve one problem, but there are still a couple more right behind it:
* Running "cargo test" tries to build a binary that links to the library to run the unit tests, but this will fail because there are unresolved symbols in the .so file (symbols that call APIs in the host program, in this case PostgreSQL functions like palloc()). In other words, the test binary is useless because it can't load the plugin.
* Linux seems fine creating a .so with unresolved symbols, but Mac and Windows need special linker flags. I can document that the linker flags are needed by consumers of my crate, but it would be nice if there was a better/supported way to create plugin-style shared libraries. I tried filing a bug with PR (https://github.com/rust-lang/rust/pull/66204) it was determined that it needed to go through the RFC process.
The simplest way to see the problem is to look at this crate: https://github.com/jeff-davis/unresolved
My problems don't end there, unfortunately. For my crate to really work, I also need to solve the problem of setjmp/longjmp at the edges between C and Rust (https://github.com/rust-lang/rfcs/issues/2625).
So, I am still not 100% sure that I understand, mostly because each package can only have one library, so this would only work if you wanted one plugin. I guess maybe if they were all in a workspace?
Having the location of the plugin might also allow "cargo install" to work.
macro_rules! mac_trait {
($i:item) => {
trait T { $i }
}
}
mac_trait! {
fn foo() {}
}
Is this making it easier to implement a trait? interface?But as others said, the point is boilerplate reduction. You can write a macro that'll automate that process away. A lot of examples are about automatically implementing certain behaviors for structs, such as equality, but you'll also see extensive macro use in ORM libraries that can generate a lot of runtime code off of a smaller table definition macro.
Anything with an explanation point is a macro invocation, by the way.
Can you explain what this means, exactly?
foo(bar)
This is a macro invocation: foo!(bar)
It's done this way because macros can have much more complex things inside the ()s than functions can. This helps both humans and computers parse such things. println!("something")
that's a macro println!("hello, world");
or todo!();
are macro invocations.#[Foo] struct Bar {}
baz!()
cat!{}
blah![]
I didn’t bother to clarify, since 5 people jumped in to explain, and I felt it was redundant.
`trait T {}` and `fn foo() {}` are used in the final code, which would be:
trait T { fn foo(){} }The macros are only about convenience on usage and reduction of boilerplate. A good example is the vec![1,2] macro call that expands to
{
let mut x = Vec::new();
x.push(1);
x.push(2);
x
}
The reason the blog post is using that example is because it's showing a new type of macro argument which accepts items (functions, structs, enums, traits and impls) and expands them correctly without any extra needs by the macro writer. fn set_document_owner(document_id: i64, user_id: i64)
where I could easily provide the arguments in the wrong order, I want fn set_document_owner(document_id: DocumentID, user_id: UserID)
where this cannot happen. Each ID type is just a one-element tuple containing an i64, which would just be pub struct DocumentID(pub i64);
pub struct UserID(pub i64);
//... and so on ...
and so on. But in order for them to behave as expected (e.g. with the == operator or when reading/writing IDs in the database), I need to write a bunch of boilerplate. This is the macro that I have now: use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
macro_rules! make_id_type {
( $name:ident ) => {
///A type-safe primary key.
#[derive(Default, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub struct $name(pub i64);
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl ToSql for $name {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
self.0.to_sql()
}
}
impl FromSql for $name {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
Ok($name(i64::column_result(value)?))
}
}
};
}
make_id_type!(DocumentID);
make_id_type!(UserID);
//... and so on ...
This is pretty much the most basic usecase for macros. You can do some more interesting stuff than just take a name and insert it into some template, because macros can actually pattern-match the phrases given to them in the macro invocation. A good example, also from the RDBMS domain, would be https://docs.diesel.rs/diesel/macro.table.html (link goes to the documentation of the macro). #define MAC_TRAIT(i) trait T { i }
MAC_TRAIT( fn foo() {} )
Except that in Rust macro arguments are typed and can dictate their own microsyntax. Macro output is assembled in the syntax tree, not as text find'n'replace, which also avoids many surprises.You don't need them to write rust applications, but they could, for example, help writing tests on 3 different backends without having to write 3 times the same testing function.
In this situation, it's different from having a type parameter, because macros won't make you change the function definition. They will just "copy and paste" the code block for you.
A good mental model is to think about this kind of macro is as a compiler callback that defines a syntax tree fragment in its body, to be evaluated when the macro is called.
The goal of macros is to deduplicate code; either inside a function or to implement traits (eg. the Debug trait, that allows printing a structure/enumeration/... recursively; this would be very boring to implement yourself every time)
Of course lisp macros are on a different level but the languages are massively different so it's not necessarily a super fair comparison.
I can see why people are worried, and it's valid, but I also trust the devs are making correct choices
Periodization is hard; it's been five years since Rust 1.0. The project is much older, but the language as we know it today has only been around half that time.
I don't know if that statement is really justified. Sure, Google is a much bigger and more influential company than Mozilla. But when taking the average over the entire company, all-of-Google supports Go much less strongly than all-of-Mozilla supports Rust, and I think that pretty much cancels each other out the company size, so I would consider both langs to have roughly equal corporate backing.
I think the actual difference is that Go has a much larger immediate audience than Rust. Go appeals primarily to developers of web service backends, who are coming from Ruby/Python/Perl/PHP and are looking for something faster and statically typed. Rust OTOH initially mostly appealed to systems developers looking for a better C/C++, although that audience has been steadily expanding since then.
I like that!
Do the primitive type inferences also work when comparing i32 vs &i32, or even better, &i32 vs &u64?
let a: f32 = 0.0;
let b: f64 = 0.0;
let this_works: f32 = &0.0 + 0.0;
let this_doesnt: f32 = &a + b;
And that's a good thing IMO, implicit promotion rules in C are one of my least favourite features of the language. It makes it so easy to write seemingly innocuous code that's completely broken.This mostly happens in small code examples and test cases/doctests, e.g. `fn main() { dbg!(42); }` only compiles because of the existence of this fallback.
Yes, I know that I can just use .S files for my assembly, but sometimes I just need to access a CSR.