These are chat archives for rust-lang/rust

31st
Jan 2019
Arnaud Esteve
@aesteve
Jan 31 09:06
Hello folks! I don't know if this is the best medium to ask my question, please redirect me (maybe to the correct piece of doc) if needed. I have trouble understanding lifetimes. Here's my use case: I have a utility function which creates a json string from a struct. I'm calling it from the main function, then log it, work pass it to other functions etc. If my utility json returns String then I can't use the String after logging it, because it has been moved. If my json utility function returns &str then the function is missing the "lifetime". I'm really not sure what the lifetime should be in my case. To my mind, this string created by the JSON utility function should be totally immutable afterwards. Could someone point me at the right place for understanding this use-case ? Thanks a lot
Ichoran
@Ichoran
Jan 31 09:24
Well, someone needs to own the String so it can be cleaned up when the owner is done. But I don't see why the logger needs to own it. Can the logger take a &str from the String?
Zakarum
@omni-viral
Jan 31 09:24
@aesteve lifetimes are tied to concept of borrowing. String is owned. &String (and &str as well) are immutably borrowed.
So since your json utility function creates string it creates String and owns it. It can't return &str because created String will be destroyed at the end of scope if not moved. Hence it must return String.
When you assign returned value in outer function to say, foo, that new variable now owns String.
If you want to log foo value you should pass &foo to the logging function, i.e. borrow it to the logging function, not give (and lose ownership), so logging function should take &String, or &str.
Arnaud Esteve
@aesteve
Jan 31 09:25
ok I see
Thanks for the pointer
Zakarum
@omni-viral
Jan 31 09:25
The difference between &String and &str is that &String can be created only from String and &str can be created from stirng literal, and what is more important it can reference part of the whole string
Cuz it is pointer + size under the hood
So usually you never see &String type
Arnaud Esteve
@aesteve
Jan 31 09:26
I think my issue was that some other function I was defining (which working with the json String) had such signature pub fn do_something_with(str: String) and not do_something_with(str: &String)
Zakarum
@omni-viral
Jan 31 09:27
Yeah
Except prefer do_something_with(str: &str)
Arnaud Esteve
@aesteve
Jan 31 09:27
yes, I changed it thanks to your previous comment
thanks
Zakarum
@omni-viral
Jan 31 09:28
you're welcome
Arnaud Esteve
@aesteve
Jan 31 09:28
ok, so a "creator" utility, should always return String, or MyStruct
Zakarum
@omni-viral
Jan 31 09:28
almost always
Arnaud Esteve
@aesteve
Jan 31 09:28
the "caller" of the creator, should then use &returnedValue to pass it to other functions that need to read some values
Zakarum
@omni-viral
Jan 31 09:29
Yes. Readers should only borrow
Function should take ownership to store value, or destroy
With Copy type you can always pass by value though
Ichoran
@Ichoran
Jan 31 09:30
Unless the thing is really small and Copy.
Arnaud Esteve
@aesteve
Jan 31 09:31
I think I have another use case I'm not sure about
I have a struct, say struct Person { name: String } and another utility function say: fn create_dog(master: &Person) { Dog { master_name: &master.name } }
this would not compile
Zakarum
@omni-viral
Jan 31 09:33
In this case you can write master.name.clone()
Arnaud Esteve
@aesteve
Jan 31 09:33
so I added: fn create_dog(master: &Person) { Dog { master_name: &master.name.to_owned() } }
Zakarum
@omni-viral
Jan 31 09:33
You need to remove last &
&<expr> borrows result of expression
Arnaud Esteve
@aesteve
Jan 31 09:33
ye sorry
typo in chat
i wasn't using & in my actual code
Zakarum
@omni-viral
Jan 31 09:34
So what it was actually?
Dog { master_name: master.name.to_owned() }?
Arnaud Esteve
@aesteve
Jan 31 09:34
ye
just needed confirmation it was the right thing to do
Zakarum
@omni-viral
Jan 31 09:35
Use clone
to_owned is more general
Well. You can use either
Arnaud Esteve
@aesteve
Jan 31 09:36
I've seen that it does the same by navigating in sources to to_owned() but yes, if it's more clear by using clone() I'll do so, no problem
Since Person is coming from a JSON record, I was wondering if I should use String or str
Zakarum
@omni-viral
Jan 31 09:46
If your JSON is stored in String, you possibly can use &str that referns to the part of the whole json
But this way you have to specify that function that parses json takes unparsed json as &'a String and returns found value as &'a str
Arnaud Esteve
@aesteve
Jan 31 09:50
oh sorry, my question was unclear
I was wondering if field name in struct Person should be a stror a String
Tim Robinson
@1tgr
Jan 31 09:52
It depends how you’re parsing your JSON, but String is the safe option
Serde knows how to deserialise a str, but it relies on keeping the original input data alive longer than your Person
Arnaud Esteve
@aesteve
Jan 31 09:53
I'm using serde_json, it looked like the best solution
oh, I see
that's probably a very bad idea then, since Person, the original json, etc. should no longer exist once I'm done reading it and publishing it (log or something)
Denis Lisov
@tanriol
Jan 31 09:57
@omni-viral @1tgr Don't use a &str, use a Cow instead. If there are backslash-escapes in the JSON string, you cannot borrow it, so you need an owned variant too.
Zakarum
@omni-viral
Jan 31 10:01
@tanriol You're right
Cow<'a, str>
Arnaud Esteve
@aesteve
Jan 31 10:03
which one of my types should be a Cow ? sorry :\
Person, Person.name, the original json string ? not sure I understand. Sorry to bother you
Zakarum
@omni-viral
Jan 31 11:17
Return type of json parsing function
fn get_person_name_from_json<'a>(json: &'a str) -> Cow<'a, str>
Arnaud Esteve
@aesteve
Jan 31 11:20
oh
I'm returning a Person from this function.
then maybe log the name, pass it to the Dog creator function, etc.
Zakarum
@omni-viral
Jan 31 11:21
I think you should not bother and just allocate String when parsing json
And clone it for Dog
Arnaud Esteve
@aesteve
Jan 31 11:21
ok, that's what I did so far. Thanks a lot for your help
Zakarum
@omni-viral
Jan 31 11:22
On top of that you can possibly wrap String into Rc<String> to not clone it, but clone pointer
Since you probably not intent to mutate string anyway
Arnaud Esteve
@aesteve
Jan 31 11:23
sure I won't
Zakarum
@omni-viral
Jan 31 11:23
Arc instead of Rc to make it usable over multiple threads
Arnaud Esteve
@aesteve
Jan 31 11:23
and serde_json will understand that Arc<String> well ?
Zakarum
@omni-viral
Jan 31 11:23
To serialize it?
Arnaud Esteve
@aesteve
Jan 31 11:23
deserialize it
Person comes from json, it's never serialized tho
Tim Robinson
@1tgr
Jan 31 11:24
Rc or Arc not a great idea in Serde deserialisation
Arnaud Esteve
@aesteve
Jan 31 11:24
ok
Tim Robinson
@1tgr
Jan 31 11:24
They’re equivalent to plain String, it works but Serde does not try to do anything with sharing references
Zakarum
@omni-viral
Jan 31 11:25
Rc and Arc are not (de)serializable by serde it seems
Tim Robinson
@1tgr
Jan 31 11:25
There’s an optional feature that adds support
Zakarum
@omni-viral
Jan 31 11:25
Ah
Tim Robinson
@1tgr
Jan 31 11:25
But it’s fairly minimal
Zakarum
@omni-viral
Jan 31 11:25
I see. docs.rs doesnt reveal it
Tim Robinson
@1tgr
Jan 31 11:25
String will work fine inside your Person
Zakarum
@omni-viral
Jan 31 11:26
I guess it treats them as &T
Tim Robinson
@1tgr
Jan 31 11:26
If you’re deserialising millions of Person and you’re getting performance problems you could consider alternatives
@omni-viral it treats Rc<T> like T
Arnaud Esteve
@aesteve
Jan 31 11:27
that'll probably be the case (deserializing millions of records) in the life of the program, but then I'll look into it
Zakarum
@omni-viral
Jan 31 11:27
@1tgr well, yeah
Tim Robinson
@1tgr
Jan 31 11:28
Sure, serializing &T is like serializing T
But deserializing &T is a lot more complicated
Zakarum
@omni-viral
Jan 31 11:28
It basically doesn't deserialize &T
Except when T is str or [u8]
Matteo Ferretti
@ZER0
Jan 31 13:50
Hi there! I'm writing a parse function, that returns a Result. So far, the Error part was just a simple enum, that lists all the exceptions. Now, I'd like to have at least two different level of errors, the one critical and just warnings. I was then thinking to have two enums like ParseException and ParseWarning, and have ParseError with exception(ParseException), warning(ParseWarning) as fields. I was wondering if that's okay or if there is a better way in rust.
Tim Robinson
@1tgr
Jan 31 13:53
But in the success case you can still return warnings?
Can you return warnings in the error case?
Matteo Ferretti
@ZER0
Jan 31 13:53
In this specific case, no: the "warning" is still an error, but a kind of error I can recover from.
Ingvar Stepanyan
@RReverser
Jan 31 13:54
So parsing should continue, not exit, right?
Matteo Ferretti
@ZER0
Jan 31 13:55
Well, it's still an error, so that part of the parse should stop, that's why I added in the "errors" category.
Tim Robinson
@1tgr
Jan 31 13:55
I think your design is fine
Result<T, ParseError>
Matteo Ferretti
@ZER0
Jan 31 13:55
It's basically said "okay, this is wrong, but I can ignore it and continue with the rest" oppose at "okay, this is wrong, and I cannot continue"
Tim Robinson
@1tgr
Jan 31 13:56
enum ParseError { Exception(ParseException), Warning(ParseWarning) }
Matteo Ferretti
@ZER0
Jan 31 13:57
Yes, that is currently my design. I was wondering if there was a different or better way.
Tim Robinson
@1tgr
Jan 31 13:58
Based on your description I'd do the same thing
Matteo Ferretti
@ZER0
Jan 31 14:00
Okay, thanks. It's just that seems to be a bit verbose when I "throw" the error: Err(ParseError::Exception(ParseException::MyError(...))) and maybe there was a way to do such thing in a more concise way that I didn't consider
Denis Lisov
@tanriol
Jan 31 14:01
How about impl From<ParseException> for ParseError?
Matteo Ferretti
@ZER0
Jan 31 14:09
@tanriol I'm reading https://doc.rust-lang.org/std/convert/trait.From.html it might be a good alternative, thanks!
Arnaud Esteve
@aesteve
Jan 31 14:09

Sorry to bother. I have another basic question. I'm having:

struct Change { INSERT, UPDATE, DELETE }
struct ChangeLog { type_: Change, before: Option<String>, after: Option<String> }

and I'm trying to implement fmt::Debug for this type. My idea was to go with:

let change = match self.type_ { 
  Change::DELETE => self.after.before.unwrap(),
  _ => self.after.unwrap()
};
write!(f, "Change {}. Log {}", data)

But the "unwrap" call doesn't work since I'm not allowed to borrow here. I'm not sure which function I should use instead.

Denis Lisov
@tanriol
Jan 31 14:13
.as_ref()
Arnaud Esteve
@aesteve
Jan 31 14:13
oh ok, thank you.
Denis Lisov
@tanriol
Jan 31 14:13
But your data structure is bad
Arnaud Esteve
@aesteve
Jan 31 14:13
it's handed to me in JSON, I'd not have written this way
Denis Lisov
@tanriol
Jan 31 14:14
Are you using serde?
Arnaud Esteve
@aesteve
Jan 31 14:14
yes
Denis Lisov
@tanriol
Jan 31 14:15
Arnaud Esteve
@aesteve
Jan 31 14:19

Thank you. I've read it an hour ago in order to know if enum would be handled by serde. But I didn't have the data modeling in head. I think I see your point now. With serde I should be able to define something like

enum ChangeLog { 
  InsertLog {  after: String }, 
  UpdateLog { before: String, after: String }, 
  DeleteLog { before: String }
 }

Am i right ?

Denis Lisov
@tanriol
Jan 31 14:22
Yes, with tag = "type" and some rename attributes to match the exact strings you need.
Arnaud Esteve
@aesteve
Jan 31 14:22
Ok, that's a really nice feature.
Farhan Ahmed
@IMacronaut_twitter
Jan 31 14:23
Hello. Is anyone able to install cargo-tarpaulin using Rust 1.32? I keep getting ptrace errors when installing following the instructions in the readme.
Hoping to use version 0.7 for cargo-tarpaulin.
Ingvar Stepanyan
@RReverser
Jan 31 14:51
@aesteve Probably having Log in the end of each variant is a bit excessive given that it's already there on the type name too
And after you remove it you can just apply rename_all on entire enum rather than rename each variant individually
Like
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
enum ChangeLog { 
  Insert { after: String }, 
  Update { before: String, after: String }, 
  Delete { before: String }
}
Arnaud Esteve
@aesteve
Jan 31 14:55

wow, thanks @RReverser @tanriol , I can even pattern match:

let (operation, log) = self match {
   Insert { after } => ("INSERT", log),
}

both combined is really really cool.

Ingvar Stepanyan
@RReverser
Jan 31 14:55
self match doesn't look like right syntax but yeah
Michal 'vorner' Vaner
@vorner
Jan 31 14:56
Yes, it's match self {...self is just a variable. Or, mostly so.
Arnaud Esteve
@aesteve
Jan 31 15:02
yes, when typing in chat I make a lot of mistake. self match is scala match style
sorry 🤦🏻‍♂️
Arnaud Esteve
@aesteve
Jan 31 15:18

One last thing, and I'll stop trying your patience much longer.
I can't find in the book about enums if it's possible that different variants have some common impl. In my case, what if:

enum ChangeLog {
  Insert {  operation_id: u32, user_mask: String, after: String }, 
  Update { operation_id: u32, user_mask: String, , before: String, after: String }, 
  Delete { operation_id: u32, user_mask: String, before: String }
 }

I wouldn't mind having something like Insert { common: CommonLog, after: String } in the receiving data structure, I'd deal with it.
But I can't find what in serde would sound like "flatten all remaining fields into the property common". Maybe something around default or transparent ?

Ingvar Stepanyan
@RReverser
Jan 31 15:19
serde(flatten)?
But you probably want to go the other way around instead
Have outer struct that has common fields, and enum nested inside - it would be cleaner than repeating common: CommonLog in each variant
#[derive(Deserialize)]
struct Operation {
  #[serde(rename = "operation_id")]
  id: u32,
  #[serde(flatten)]
  kind: OperationKind,
}

#[derive(Deserialize)]
#[...]
enum OperationKind {
  ...
}
something like this perhaps
Arnaud Esteve
@aesteve
Jan 31 16:35
I think I get it, I'll try. Thank you
Arnaud Esteve
@aesteve
Jan 31 17:26
This works like a charm. First: thanks to all of you folks. Great help, great advices, very welcoming. Second: huge props to the folks behind serde. That's a very nice library.
What a nice first contact with a language / environment 🥳
Tim Robinson
@1tgr
Jan 31 17:27
Serde is certainly the best JSON library I've used, in any language
Flexible, cleanly designed, and fast
Ingvar Stepanyan
@RReverser
Jan 31 20:36
nitpick: Serde is not a JSON library
Ichoran
@Ichoran
Jan 31 20:47
Pretty impressive that it manages to be the best JSON library without even being a JSON library :joy:
John
@onFireForGod_gitlab
Jan 31 21:36

I am organizing a program that accepts data and store that data into a db. I use Json<Obj> format and I use diesel to save Obj into db.
my current structure looks like:

.
├── auth.rs
├── bin
│   └── seed.rs
├── config.rs
├── db
│   ├── mod.rs
│   └── users.rs
├── main.rs
├── models.rs
├── routes
│   ├── mod.rs
│   └── users.rs
└── schema.rs

routes::users and db::routes access the same obj . I will have multiple of these obj that get accessed by both routes and db. Where should I store these objects by rust conventions?

I was thinking to add another mod with interfaces and drop them all there