How to restrict the construction of struct? - rust

Is it possible to forbid creating an instances directly from member initialization?
e.g.
pub struct Person {
name: String,
age: u8,
}
impl Person {
pub fn new(age: u8, name: String) -> Person {
if age < 18 {
panic!("Can not create instance");
}
Person { age, name }
}
}
I can still use
Person {
age: 6,
name: String::from("mike")
}
to create instance. Is there anyway to avoid this?

Answer to question
You cannot create that struct from member initialization, because members are by default private and cannot be used directly. Only the immediate module and its submodules can access private fields, functions, ... (see the book about visibility).
Your example works, because your function is in that certain scope.
mod foo {
pub struct Person {
name: String,
age: u8,
}
impl Person {
pub fn new(age: u8, name: String) -> Person {
if age < 18 {
panic!("Can not create instance");
}
Person { age, name }
}
}
}
use foo::Person; // imagine foo is an external crate
fn main() {
let p = Person {
name: String::from("Peter"),
age: 8,
};
}
(Playground)
error[E0451]: field `name` of struct `Person` is private
error[E0451]: field `age` of struct `Person` is private
Make it possible to create a struct from the outside
On the other hand, if you want to make it possible to create an instance by member initialization, use the pub keyword in front of all members.
pub struct Person {
pub name: String,
pub age: u8,
}
Make it possible to access the fields, but not creating a struct from the outside
Please see KittenOverflows answer for a better approach to this.
--
Sometimes it's useful to let the user of your crate access the members directly, but you want to restrict the creation of an instance to your "constructors". Just add a private field.
pub struct Person {
pub name: String,
pub age: u8,
_private: ()
}
Because you cannot access _private, you cannot create an instance of Person directly.
Also the _private field prevents creating a struct via the update syntax, so this fails:
/// same code from above
fn main() {
let p = Person::new(8, String::from("Peter"));
let p2 = Person { age: 10, ..p };
}
error[E0451]: field `_private` of struct `foo::Person` is private
--> src/main.rs:27:34
|
27 | let p2 = Person { age: 10, ..p };
| ^ field `_private` is private

For Rust >= 1.40.0, consider applying the non_exhaustive attribute to your struct.
// Callers from outside my crate can't directly construct me
// or exhaustively match on my fields!
#[non_exhaustive]
pub struct Settings {
pub smarf: i32,
pub narf: i32,
}
More info in the 1.40.0 release notes.

Related

Can I declare an enum value that takes a String or &str without needing additional functions?

I have an enum with a String:
enum MyLovelyEnum {
Thing(String),
}
For tests, I would like to be able to pass in a &'static str to avoid MyLovelyEnum::Thing("abc".to_string) over and over.
I found that you can do this nicely with structs with a constructor:
// From: https://hermanradtke.com/2015/05/06/creating-a-rust-function-that-accepts-string-or-str.html
struct Person {
name: String,
}
impl Person {
fn new<S: Into<String>>(name: S) -> Person {
Person { name: name.into() }
}
}
fn main() {
let person = Person::new("Herman");
let person = Person::new("Herman".to_string());
}
I know I can use lifetimes or Cow as described in What's the best practice for str/String values in Rust enums? or I can make my own function.
Is there something close to the example in the blog post for enums? e.g.
// this is the kind of thing I am after but this specifically is not correct syntax
enum MyLovelyEnum {
Thing<S: Into<String>>(S)
}
You can create a generic enum:
enum MyLovelyEnum<S>
where
S: Into<String>,
{
Thing(S),
}
MyLovelyEnum::Thing("a");
MyLovelyEnum::Thing("b".to_string());
I likely wouldn't do that in my code, instead opting to create a constructor, much like the blog post you linked:
enum MyLovelyEnum {
Thing(String),
}
impl MyLovelyEnum {
fn thing(s: impl Into<String>) -> Self {
MyLovelyEnum::Thing(s.into())
}
}
MyLovelyEnum::thing("a");
MyLovelyEnum::thing("b".to_string());

How does one map a struct to another one in Rust?

In others languages (like Java), libraries are available for mapping object fields to another object (like mapstruct). It is useful indeed for isolating controller and service from each other.
PersonDto personDto = mapper.businessToDto(personBusiness);
And I can't find how to do it with Rust ? I didn't find any libraries helping with this, or any way to do it. Any resource would be very appreciated !
In rust you usually do it via From trait:
struct Person {
name: String,
age: u8,
}
struct PersonDto {
name: String,
age: u64,
}
impl From<Person> for PersonDto {
fn from(p: Person) -> Self {
Self {
name: p.name,
age: p.age.into(),
}
}
}
So you can make an Into conversion:
let person = Person { name: "Alex".to_string(), age: 42 };
let person_dto: PersonDto = person.into();
// or via an explicit `T::from:
let person_dto = PersonDto::from(person);

How do I read attributes programatically in Rust

I'd like to read attributes programatically. For example, I have a struct which has attributes attached to each field:
#[derive(Clone, Debug, PartialEq, Message)]
pub struct Person {
#[prost(string, tag="1")]
pub name: String,
/// Unique ID number for this person.
#[prost(int32, tag="2")]
pub id: i32,
#[prost(string, tag="3")]
pub email: String,
#[prost(message, repeated, tag="4")]
pub phones: Vec<person::PhoneNumber>,
}
(source)
I'd like to find the tag associated with the email field.
I expect there is some code like this to get the tag at runtime:
let tag = Person::email::prost::tag;
Since attributes are read only at compile time you need to write a procedural macro to solve such issue.
You can find information with following designators in your macro.
Field name with ident
Attribute contents with meta
After you find your field name and your meta, then you can match the stringified result with given input parameter in macro like following:
macro_rules! my_macro {
(struct $name:ident {
$(#[$field_attribute:meta] $field_name:ident: $field_type:ty,)*
}) => {
struct $name {
$(#[$field_attribute] $field_name: $field_type,)*
}
impl $name {
fn get_field_attribute(field_name_prm : &str) -> &'static str {
let fields = vec![$(stringify!($field_name,$field_attribute)),*];
let mut ret_val = "Field Not Found";
fields.iter().for_each(|field_str| {
let parts : Vec<&str> = field_str.split(' ').collect();
if parts[0] == field_name_prm{
ret_val = parts[2];
}
});
ret_val
}
}
}
}
my_macro! {
struct S {
#[serde(default)]
field1: String,
#[serde(default)]
field2: String,
}
}
Please note that it assumes that every field in the struct has an attribute. And every field declaration is ending with , including last field. But with some modification on regex you can make it available for optional attributes as well.
Here working solution in Playground
For further info about designators here is the reference
Also you can take a quick look for procedural macros here

Why is a lifetime unused on a struct that I have a reference to?

The following won't compile:
struct Person<'z> {
street_address: String,
postcode: String,
city: String,
company_name: String,
position: String,
annual_income: usize,
}
struct PersonBuilder<'z> {
person: &'z Person<'z>,
}
impl<'z> PersonBuilder<'z> {
fn new() -> PersonBuilder<'z> {
PersonBuilder {
person: Person {
street_address: "".to_string(),
postcode: "".to_string(),
city: "".to_string(),
company_name: "".to_string(),
position: "".to_string(),
annual_income: 0,
},
}
}
}
error[E0392]: parameter `'z` is never used
--> src/main.rs:1:15
|
1 | struct Person<'z> {
| ^^ unused type parameter
|
= help: consider removing `'z` or using a marker such as `std::marker::PhantomData`
But that's not true, is it? I mean, PersonBuilder keeps a reference to a person as Person<'z> and when I instantiate it with PersonBuilder::new(), clearly the type parameter is in play to tie the two lifetimes together. So what's going on here?
The compiler is completely correct here; 'z is not used on Person. Remove it from Person and from PersonBuilder:
struct Person { /* ... */ }
struct PersonBuilder<'z> {
person: &'z Person,
}
One you remove that, what you are trying to do is impossible. See Is there any way to return a reference to a variable created in a function?. Remove all lifetimes and references from your code and it will work.
PersonBuilder keeps a reference to a person
Yes, that's what &Person would be.
as Person<'z>
Person<'z> indicates that the Person struct contains a reference of the specified lifetime. &'z Person is a reference to a person with the specified lifetime.
instantiate it with PersonBuilder::new(), clearly the type parameter
Which "type parameter" are you referring to, as I don't see any in this code. While it's true that lifetimes and type parameters are both kinds of generics, they are different kinds of generics and are not treated the same.
You may also be interested in the Default trait:
#[derive(Debug, Default)]
struct Person { /* ... */ }
impl PersonBuilder {
fn new() -> PersonBuilder {
PersonBuilder {
person: Person::default(),
}
}
}

In memory database design

I am trying to create an in-memory database using HashMap. I have a struct Person:
struct Person {
id: i64,
name: String,
}
impl Person {
pub fn new(id: i64, name: &str) -> Person {
Person {
id: id,
name: name.to_string(),
}
}
pub fn set_name(&mut self, name: &str) {
self.name = name.to_string();
}
}
And I have struct Database:
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
struct Database {
db: Arc<Mutex<HashMap<i64, Person>>>,
}
impl Database {
pub fn new() -> Database {
Database {
db: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn add_person(&mut self, id: i64, person: Person) {
self.db.lock().unwrap().insert(id, person);
}
pub fn get_person(&self, id: i64) -> Option<&mut Person> {
self.db.lock().unwrap().get_mut(&id)
}
}
And code to use this database:
let mut db = Database::new();
db.add_person(1, Person::new(1, "Bob"));
I want to change person's name:
let mut person = db.get_person(1).unwrap();
person.set_name("Bill");
The complete code in the Rust playground.
When compiling, I get a problem with Rust lifetimes:
error[E0597]: borrowed value does not live long enough
--> src/main.rs:39:9
|
39 | self.db.lock().unwrap().get_mut(&id)
| ^^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
40 | }
| - temporary value only lives until here
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 38:5...
--> src/main.rs:38:5
|
38 | / pub fn get_person(&self, id: i64) -> Option<&mut Person> {
39 | | self.db.lock().unwrap().get_mut(&id)
40 | | }
| |_____^
How to implement this approach?
The compiler rejects your code because it violates the correctness model enforced by Rust and could cause crashes. For one, if get_person() were allowed to compile, one might call it from two threads and modify the underlying object without the protection of the mutex, causing data races on the String object inside. Worse, one could wreak havoc even in a single-threaded scenario by doing something like:
let mut ref1 = db.get_person(1).unwrap();
let mut ref2 = db.get_person(1).unwrap();
// ERROR - two mutable references to the same object!
let vec: Vec<Person> = vec![];
vec.push(*ref1); // move referenced object to the vector
println!(*ref2); // CRASH - object already moved
To correct the code, you need to adjust your design to satisfy the following constraints:
No reference can be allowed to outlive the referred-to object;
During the lifetime of a mutable reference, no other reference (mutable or immutable) to the object may exist..
The add_person method already complies with both rules because it eats the object you pass it, moving it to the database.
What if we modified get_person() to return an immutable reference?
pub fn get_person(&self, id: i64) -> Option<&Person> {
self.db.lock().unwrap().get(&id)
}
Even this seemingly innocent version still doesn't compile! That is because it violates the first rule. Rust cannot statically prove that the reference will not outlive the database itself, since the database is allocated on the heap and reference-counted, so it can be dropped at any time. But even if it were possible to somehow explicitly declare the lifetime of the reference to one that provably couldn't outlive the database, retaining the reference after unlocking the mutex would allow data races. There is simply no way to implement get_person() and still retain thread safety.
A thread-safe implementation of a read can opt to return a copy of the data. Person can implement the clone() method and get_person() can invoke it like this:
#[derive(Clone)]
struct Person {
id: i64,
name: String
}
// ...
pub fn get_person(&self, id: i64) -> Option<Person> {
self.db.lock().unwrap().get(&id).cloned()
}
This kind of change won't work for the other use case of get_person(), where the method is used for the express purpose of obtaining a mutable reference to change the person in the database. Obtaining a mutable reference to a shared resource violates the second rule and could lead to crashes as shown above. There are several ways to make it safe. One is by providing a proxy in the database for setting each Person field:
pub fn set_person_name(&self, id: i64, new_name: String) -> bool {
match self.db.lock().unwrap().get_mut(&id) {
Some(mut person) => {
person.name = new_name;
true
}
None => false
}
}
As the number of fields on Person grows, this would quickly get tedious. It could also get slow, as a separate mutex lock would have to be acquired for each access.
There is fortunately a better way to implement modification of the entry. Remember that using a mutable reference violates the rules unless Rust can prove that the reference won't "escape" the block where it is being used. This can be ensured by inverting the control - instead of a get_person() that returns the mutable reference, we can introduce a modify_person() that passes the mutable reference to a callable, which can do whatever it likes with it. For example:
pub fn modify_person<F>(&self, id: i64, f: F) where F: FnOnce(Option<&mut Person>) {
f(self.db.lock().unwrap().get_mut(&id))
}
The usage would look like this:
fn main() {
let mut db = Database::new();
db.add_person(1, Person::new(1, "Bob"));
assert!(db.get_person(1).unwrap().name == "Bob");
db.modify_person(1, |person| {
person.unwrap().set_name("Bill");
});
}
Finally, if you're worried about the performance of get_person() cloning Person for the sole reason of inspecting it, it is trivial to create an immutable version of modify_person that serves as a non-copying alternative to get_person():
pub fn read_person<F, R>(&self, id: i64, f: F) -> R
where F: FnOnce(Option<&Person>) -> R {
f(self.db.lock().unwrap().get(&id))
}
Besides taking a shared reference to Person, read_person is also allowing the closure to return a value if it chooses, typically something it picks up from the object it receives. Its usage would be similar to the usage of modify_person, with the added possibility of returning a value:
// if Person had an "age" field, we could obtain it like this:
let person_age = db.read_person(1, |person| person.unwrap().age);
// equivalent to the copying definition of db.get_person():
let person_copy = db.read_person(1, |person| person.cloned());
This post use the pattern cited as "inversion of control" in the well explained answer and just add only sugar for demonstrating another api for an in-memory db.
With a macro rule it is possible to expose a db client api like that:
fn main() {
let db = Database::new();
let person_id = 1234;
// probably not the best design choice to duplicate the person_id,
// for the purpose here is not important
db.add_person(person_id, Person::new(person_id, "Bob"));
db_update!(db #person_id => set_name("Gambadilegno"));
println!("your new name is {}", db.get_person(person_id).unwrap().name);
}
My opinionated macro has the format:
<database_instance> #<object_key> => <method_name>(<args>)
Below the macro implementation and the full demo code:
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
macro_rules! db_update {
($db:ident # $id:expr => $meth:tt($($args:tt)*)) => {
$db.modify_person($id, |person| {
person.unwrap().$meth($($args)*);
});
};
}
#[derive(Clone)]
struct Person {
id: u64,
name: String,
}
impl Person {
pub fn new(id: u64, name: &str) -> Person {
Person {
id: id,
name: name.to_string(),
}
}
fn set_name(&mut self, value: &str) {
self.name = value.to_string();
}
}
struct Database {
db: Arc<Mutex<HashMap<u64, Person>>>, // access from different threads
}
impl Database {
pub fn new() -> Database {
Database {
db: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn add_person(&self, id: u64, person: Person) {
self.db.lock().unwrap().insert(id, person);
}
pub fn modify_person<F>(&self, id: u64, f: F)
where
F: FnOnce(Option<&mut Person>),
{
f(self.db.lock().unwrap().get_mut(&id));
}
pub fn get_person(&self, id: u64) -> Option<Person> {
self.db.lock().unwrap().get(&id).cloned()
}
}

Resources