Rust private struct fields and defaults and destructuring? - rust

Let's say I create a struct.
mod foostruct {
#[derive(Default)]
pub struct Foo {
a: u64,
pub b: u64,
}
}
Is it true that this struct, because of the visibility rules can not be created externally with a default,
Foo { b: 42, ..Default::default() }
And, also cannot have it's visible members destructed?
let Foo { b, ... } = foo;
I was just having a conversation with a friend and both of these were brought up, and I just never thought of this. I was previously always using builders, and hadn't considered destruction/defaults as a detriment to the pattern.

Both of these bits of code are sugar for something else, so we can reason about their characteristics by examining what they desugar to.
Foo { b: 42, ..Default::default() }
Desugars to roughly:
{
let temp: Foo = Default::default();
Foo { b: 42, a: temp.a }
}
This is disallowed, because Foo::a is not visible.
let Foo { b, .. } = foo;
Desugars to this (with an additional static type-check that foo is indeed a Foo):
let b = foo.b;
This is allowed because the private fields are never actually read.

Related

Prevent value from moving after function call

I have a rust program that I am creating that repeatedly runs a function in a thread, which causes a value moved error.
Here's some example code (pretty much the same as mine but simplified to get to the point and with a few names changed around)
use std::{thread}
struct Foo {
bar: bool
}
impl Foo {
fn new() -> Self {
Foo { bar: false };
}
fn do_something2(self) {
// do something
// technically could be simplified here to self.bar = !some_condition, but someone will
// probably complain about it, not really relevant to the issue anyways
if some_condition {
self.bar = false;
}
}
}
fn do_something(mut foo: Foo) {
foo.bar = true;
thread::spawn(|| {
while foo.bar {
foo.do_something2();
}
});
}
fn main() {
let mut foo = Foo::new();
do_something(&mut foo);
// other code
}
I am not sure how I would stop the variable from being moved. In this example, it could technically be avoided by implementing the Copy trait, but my struct has a Vec as one of the values, so I cannot use Copy and need to find a different way.
First you'll want do_something to take a reference rather than an owned value. You can do that like so:
fn do_something(foo: &mut Foo) { ... }
The method Foo::doo_something2 should also be changed to take a mutable reference:
fn do_something2(&mut self) { ... }
Once you do that you'll encounter a new error. thread::spawn has no way to prove that the reference outlives the thread that is being created. Lucky for you there is a new feature in the standard library called "scoped threads" that allows you to prove to the compiler that foo won't be dropped before the child thread terminates.
You can use scoped threads like so:
fn do_something(foo: &mut Foo) {
foo.bar = true;
thread::scope(|scope| {
scope.spawn(|| {
while foo.bar {
foo.do_something2();
}
});
});
}

Is there any way to inline a const inside a doc comment (rendered by cargo doc)?

With "default" constructors, it can be useful to document what theā€¦ defaults are. If this is textually defined in the doc and separately defined as a literal or a static / const, the two can get out of sync:
impl Foo {
/// Creates a [Foo] with a `bar` of 3.
fn new() -> Foo { Foo::new_with_bar(5) }
/// Creates a [Foo] with the provided `bar`.
fn new_with_bar(bar: usize) -> Foo { Foo { bar } }
}
It's possible to extract the literal to a const or static and link to that, but then the reader has to go through the indirection to know what the value is, and the const / static has to be pub or cargo doc complains and refuses to link to it.
Is there any way to substitute the const value in the docstring instead of linking to it? Or some other method which would avoid the indirection? aka
const DEFAULT_BAR: usize = 5
impl Foo {
/// Creates a [Foo] with a `bar` of ???DEFAULT_BAR???.
fn new() -> Foo { Foo::new_with_bar(DEFAULT_BAR) }
}
should be rendered as:
pub fn new() -> Foo
Creates a Foo with a bar of 5.
Although similar, How to embed a Rust macro variable into documentation? doesn't seem to apply here. [doc] complains about an unexpected token when given a name as a parameter (even a const str) and I don't know if a wrapping macro can force the substitution of a const.
On stable there isn't really an easy solution to doing this, without requiring a specialized macro for each type/method. So the easiest is to fallback to using a const DEFAULT_BAR and referencing it in the docs (which you wanted to avoid.)
However, there's a rather new nightly feature extended_key_value_attributes (see issue #78835 and PR 78837.)
Besides requiring one of the latest nightly builds. It will also be slightly cumbersome to use for your use case (in its current state). This is because it either requires the use of literals, which excludes using const DEFAULT_BAR. Alternatively, you can use a macro which expands to 5, which is the cumbersome solution.
#![feature(extended_key_value_attributes)]
struct Foo {
bar: usize,
}
macro_rules! default_bar {
() => {
5
};
}
impl Foo {
/// Creates a [Foo] with a `bar` of
#[doc = concat!(default_bar!(), ".")]
fn new() -> Foo {
Foo::new_with_bar(default_bar!())
}
/// Creates a [Foo] with the provided `bar`.
fn new_with_bar(bar: usize) -> Foo {
Foo { bar }
}
}
The above works on rustc 1.50.0-nightly (bb1fbbf84 2020-12-22)
Note that you have to use concat!, as otherwise default_bar needs to expand into a string. So if you don't need e.g. ".", then just use an empty string, e.g. concat!("", default_bar!()).
This works in Rust 1.47:
struct Foo { bar: usize }
macro_rules! impl_foo {
($bar_def:expr) => { impl_foo!(# $bar_def, stringify!($bar_def)); };
(# $bar_def:expr, $bar_def_str:expr) => {
impl Foo {
/// Creates a [Foo] with a `bar` of
#[doc = $bar_def_str]
///.
fn new() -> Foo { Foo::new_with_bar($bar_def) }
/// Creates a [Foo] with the provided `bar`.
fn new_with_bar(bar: usize) -> Foo { Foo { bar } }
}
}
}
impl_foo!(3);
You can use the paste to avoid the redirection:
use paste::paste;
struct Foo { bar: usize }
macro_rules! impl_foo {
($bar_def:expr) => {
paste! {
impl Foo {
#[doc = "Creates a [Foo] with a `bar` of " $bar_def "."]
fn new() -> Foo { Foo::new_with_bar($bar_def) }
/// Creates a [Foo] with the provided `bar`.
fn new_with_bar(bar: usize) -> Foo { Foo { bar } }
}
}
}
}
impl_foo!(3);
In the future, you'll be able to skip the re-direction in the macro (or the usage of paste!) by using #![feature(extended_key_value_attributes)], as described in vallentin's answer.
See also:
The doc-comment crate, which uses this trick to allow documenting an item using an external Markdown file.
How to embed a Rust macro variable into documentation?

How can I set only some struct members to their default values?

I have struct B:
struct A {}
struct B {
a: A,
b: u32,
c: i8,
z: usize,
}
A does not have a Default implementation and no default that makes sense. The fields b..z all need to be initialised to the default (in this case, 0), and A will be initialised to some runtime value:
let b = B {
a: some_a(),
b: 0,
c: 0,
z: 0,
};
This is not ideal.
Even worse, if an aa were to be added to the struct, there would need to be another member added to the initialiser. Using ..Default::default() doesn't work because B doesn't have a Default implementation, because any As in the default will be too expensive to compute to simply throw away afterwards. Furthermore, I would need an extremely long struct initialiser in the Default implementation anyway.
Is there any way to set the remaining struct members to their default values (so that b would be set to the default of u32) without writing all the member names out?
Preferably, there should be minimum change required to struct initialisers when new members are added. This means this is not ideal:
let b = B {
a: some_a(),
b: Default::default(),
c: Default::default(),
z: Default::default(),
};
If you have large structs, you can make your life easier by deriving a builder or a constructor function. There are a few crates that do this, and a popular one is derive_builder.
Using that crate, you can do something like:
use derive_builder::Builder;
// this derive will generate a struct called BBuilder
#[derive(Builder)]
struct B {
a: A,
#[builder(default = "0")]
b: u32,
#[builder(default = "0")]
c: i8,
/* ... */
#[default(default = "0")]
z: usize
}
fn main() {
let a = A { ... };
// all default values for fields b..z
let b = BBuilder::default().a(a).build().unwrap();
// or specify just some of the fields
let b = BBuilder::default()
.a(a)
.c(42)
.z(255)
.build()
.unwrap();
}
Adding new fields to B will not impact code that uses BBuilder, as long as the fields have defaults. The downside is that you get a runtime panic if you miss a required field, rather than a compilation error.
Another crate is derive-new, which is a bit simpler. You would use it like this:
use derive_new::new;
#[derive(new)]
struct B {
a: A,
#[new(default)]
b: u32,
#[new(default)]
c: i8,
/* ... */
#[new(default)]
z: usize
}
fn main() {
let a = A { ... };
// all default values for fields b..z
let b = B::new(a);
// To specify some of the default fields, you need to mutate
let mut b = B::new(a);
b.c = 42;
b.z = 255;
}
This doesn't generate any extra structs, it just adds a new method, which takes all of the non-default arguments. It is a compilation error if you miss any non-default fields out.

Is there a feature to put default names in tuples for better code completion and suggestions?

Is there a feature to put default names in tuples for better code completion and suggestions? The names would not be obligatory, just a hint.
Something like:
struct Rect(width: i32, height: i32);
let r: Rect = (1, 2);
let (a, b) = r; // names while destructuring can be anything
There are three ways to define a struct (The book Chapter 5).
Declaring an empty (zero-sized) struct
struct Foo;
Declaring a tuple struct
struct Bar(i32, u32, String);
Declaring a struct with named fields
struct Baz {
first: i32,
second: u32,
third: String,
}
There is no other way.
Destructuring works for all three variants.
let a = Foo;
let Bar(f, s, t) = Bar(3, 5, String::from("Hallo"));
let Baz { first, second, third } = Baz { first: 3, second: 5, third: String::from("Hello") };
(Playground)

Allowing both static variables and boxes as a function argument?

I have a struct, which sometimes I instantiate statically, and sometimes I'd like users to allocate on the heap. Is it possible to allow both in as arguments to a function?
pub struct MyData {
x: i32
}
static ALLOCATED_STATICALLY: MyData = MyData {x: 1};
// what should my signature be?
fn use_data(instance: Box<MyData>) {
println!("{}", instance.x);
}
fn main () {
use_data(Box::new(MyData{x: 2}));
// this doesn't work currently
use_data(ALLOCATED_STATICALLY);
}
In both instances, you can pass a pointer to the function.
pub struct MyData {
x: i32
}
static ALLOCATED_STATICALLY: MyData = MyData { x: 1 };
// what should my signature be?
fn use_data(instance: &MyData) {
println!("{}", instance.x);
}
fn main () {
use_data(&Box::new(MyData{ x: 2 }));
use_data(&ALLOCATED_STATICALLY);
}
Note that in both cases, the caller needs to use the & operator to take the address of the value. In the first call, the operator yields a &Box<MyData>, but the compiler automatically converts it to a &MyData because Box implements the Deref trait.

Resources