These are chat archives for rust-lang/rust

12th
Apr 2019
Made a bunch of revisions and corrections. Feel free to contact me to encourage any changes.
Pascal Precht
@PascalPrecht
Apr 12 10:41

Hello everyone!

I'm quite new to Rust and learn the language in my spare-time with a pet project. Since I've been doing mostly Node/JavaScript, most of my attempts to make something work in Rust is inspired by patterns that usually work in JS.

One of those things is for example setting a value on a nested object property, determined by a string that represents the path.

For example, given an object like this:

let someObject = {
  foo: {
    bar: 'yay'
  }
}

I could build a function that takes a path to the property (foo.bar) and set the value:

setValueOnObj(someObject, 'foo.bar', 'new value')

I was wondering if it's possible to do the same thing in Rust, given that everything is strictly typed.

The reason I'm trying to do it this way, is because I'm building a CLI that comes with a config command to set a config value, very similar to git config:
$ git config some.config.prop 'new-value'
Any advice, pointer, help is highly appreciated!
Denis Lisov
@tanriol
Apr 12 10:43
You're saving that config in some structured format like INI/JSON/YAML/etc. anyway, correct?
Pascal Precht
@PascalPrecht
Apr 12 10:44
That is correct.
It's in TOML and I have a corresponding struct in my code.

Which is exactly why I'm unsure if this is possible, because it means I'll somehow have to check if any of the properties in the given path even exist on the struct.

Not sure if such thing exists in Rust..

Denis Lisov
@tanriol
Apr 12 10:48
Technically this can be done. However, there's a question you may want to consider first: do you treat the config file as a user interface or as parameter storage? Do you want to, for example, preserve comments in the file that the user may have left?
Pascal Precht
@PascalPrecht
Apr 12 10:49
That's a good one.
I'd say, for now it's fine to treat the config file purely as parameter storage.
So comments can be ignored.
Denis Lisov
@tanriol
Apr 12 10:56
Then I'd look at toml::Value and the toml-query crate.
Or you may want to start looking at format-preserving toml libraries (there are multiple).
Pascal Precht
@PascalPrecht
Apr 12 10:58
WOW I had no idea toml-query crate existed :D
that seems to be perfect!
Thank you so so much
Can I ask one more question related to the same thing I'm trying to do?
Denis Lisov
@tanriol
Apr 12 10:59
Note that it seems to require toml series 0.4, while the current version is 0.5
Sure, go ahead :-)
Pascal Precht
@PascalPrecht
Apr 12 11:01

So.. considering that config values can be strings or arrays, e.g:

some_config = "foo"
some_other_config = ["foo", "bar"]

The config value can be of any of those types, but I don't know which type it is at compile time.. I wonder if I have to work with Box<> here..

Although.. given that I use clap for cli arg parsing.. I might have an idea what type I'm getting.

I'd still need to express that in a method signature tho:

pub fn write(&self, option: String,  value: ???) -> Result<(), ConfigError>
Is there a way to specify whether a type is either or?
Denis Lisov
@tanriol
Apr 12 11:03
How do you expect your user to specify the type?
Pascal Precht
@PascalPrecht
Apr 12 11:05
So I believe in practice a user would do one of these:
$ cli-tool config foo.bar value
$ cli-tool config foo.bar multiple values here
Denis Lisov
@tanriol
Apr 12 11:05
The standard "value is one of the following" type in Rust is enum.
Pascal Precht
@PascalPrecht
Apr 12 11:05
The latter would probably come in as a Vec<&str>
Ah right
Denis Lisov
@tanriol
Apr 12 11:06
I'd probably convert that into the toml::Value enum.
Pascal Precht
@PascalPrecht
Apr 12 11:06
Okay, I'm gonna look into how to do that.
Thanks again Denis! This has been very helpful
Pascal Precht
@PascalPrecht
Apr 12 12:39

@tanriol I think I'm getting there thanks to your help :)

There's one thing I run into I haven't experienced before..

The way toml-query works is by extending toml::Value. So if I want to be able to do sth. like

config.set("foo.bar", value)

I need config to be a toml::Value as well. It happens that I already have an API that reads the config from a .toml file, however that API returns a different type:

pub fn read(&self) -> Result<ProjectConfig, error::ConfigError> {
toml::from_str(&fs::read_to_string(&self.config_file).map_err(error::ConfigError::Io)?).map_err(error::ConfigError::Deserialization)
}

Which means, I can't do somethign like:

let mut config = self.read().unwrap();
config.set('foo.bar', value);

Because config is a ProjectConfig, not toml::Value. Is there a way in Rust to typecast this? Because the API actually does return a toml::Value in a way :D

Denis Lisov
@tanriol
Apr 12 12:40
What's ProjectConfig?
Pascal Precht
@PascalPrecht
Apr 12 12:41
That's the struct that represents the project configuration
looks sth. like this (although I don't think it matters):
#[derive(Serialize, Deserialize, Debug)]
pub struct ProjectConfig {
  pub artifacts_dir: String,
  pub smart_contract_sources: Vec<String>,
  pub compiler: Option<ProjectCompilerConfig>,
}
Denis Lisov
@tanriol
Apr 12 12:41
Typecast will not work, they have completely different internal representation.
Pascal Precht
@PascalPrecht
Apr 12 12:43
Is there a recommended way to handle such case? I mean I can simply copy the line of read() and it works but then I'm duplicating code..
Denis Lisov
@tanriol
Apr 12 12:43
You can convert it into a Value with Value::try_from
(sorry, I'm linking to toml 0.5 documentation while you'll need to work with 0.4 or upgrade toml-queryfor 0.5)
Pascal Precht
@PascalPrecht
Apr 12 12:45
It seems to work with 0.4 too tho!
try_from did the trick! so good :D thank you
Denis Lisov
@tanriol
Apr 12 12:47
May be a good idea to check whether the new configuration can actually be parsed into ProjectConfig before writing it out to the disk :-)
Pascal Precht
@PascalPrecht
Apr 12 12:48
True!
Is there something like try_into by any chance? :D
Denis Lisov
@tanriol
Apr 12 12:50
The next method on the same page as try_from?
Pascal Precht
@PascalPrecht
Apr 12 12:51
D'oh
Thanks man
David Holroyd
@dholroyd
Apr 12 21:36
trying to convert a section of code from Vec to ArrayVec to save that allocation. However, I'm struggling because making the change also introduces a lifetime issue that I've not been able to wrap my head around. I've tried to create a simplified playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5bdf17a7c2cfe4a502c8e9a68f4195f3
doit1() is the Vec version; doit2() is the ArrayVec version
(and doit3() is my attempt to remove irrelevant lines from consideration, while trying to keep lifetimes the same, I hope!)
David Holroyd
@dholroyd
Apr 12 21:42
....and as soon as I post that, I kinda start to see the issue with the 'b: 'a lifetime; maybe I need to rework my example!
maybe doesn't make much difference, but the same thing using fewer lifetime annotations on doit3(),
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c8e554a931248ecdb896ed6394b83473
David Holroyd
@dholroyd
Apr 12 21:49
I would obviously like to fix it; and I'm also really curious about why the Vec->ArrayVec change makes a difference
Zakarum
@omni-viral
Apr 12 22:09
@dholroyd that's why, I guess
unsafe impl<#[may_dangle] T> Drop for Vec<T>
@dholroyd BTW. How are you going to get &'b mut [[&'a mut [u8]; 1]] where 'b: 'a?
Clearly the only solution here is 'b == 'a
David Holroyd
@dholroyd
Apr 12 22:16
sorry yes, I removed 'b in the revised playground
dholroyd @dholroyd looks for may_dangle docs...
Zakarum
@omni-viral
Apr 12 22:21
Oh. I see the issue. But it is actually strange one
It works if change argument type iovecs: &'a mut [[&mut [u8]; 1]],
Or like this
iovecs: &'a mut [[&'b mut [u8]; 1]], where 'b: 'a
David Holroyd
@dholroyd
Apr 12 22:27
ah, yes - playground compiles, but my real code not (of course!)
these two types are declared with different lifetimes... ...but data from 'sock' flows into 'iovecs' here
never seen that kind of 'flows into' message before
Zakarum
@omni-viral
Apr 12 22:29
It's all because &mut T is invariant over T
You have to take it into account
@dholroyd it means basically that data from sock is bound to lifetime of sock and you try to save it into variable with longer lifetime
Zakarum
@omni-viral
Apr 12 22:38
hmm. Interesting. You basically can't ever create &'a mut [&'a mut T] from ArrayVec<&mut T>.
Because you can't borrow from ArrayVec for 'a when &mut T is actially &mut 'a T
David Holroyd
@dholroyd
Apr 12 22:42
So I think rust just hates the sendmmsg() / recvmmsg() APIs, and forces me to allocate as punishment, yes?
going back to what you said earlier, maybe I will have a go at adding #[may_dangle] to ArrayVec -- although I don't really know what I'm doing ;)
conveting to lesser lifetime works for Vec, but not for mutable slice or ArrayVec
David Holroyd
@dholroyd
Apr 12 22:51
ah, #[may_dangle] may not be spoken by mortals, I see
error[E0658]: may_dangle has unstable semantics and may be removed in the future (see issue #34761)
  --> src/lib.rs:97:13
   |
97 | unsafe impl<#[may_dangle]  A: Array> Drop for ArrayVec<A> {
   |             ^^^^^^^^^^^^^
Zakarum
@omni-viral
Apr 12 22:53
Found the issue
David Holroyd
@dholroyd
Apr 12 22:53
oh? :D
Try to uncomment fn bar
Compare it with fn foo
which works
David Holroyd
@dholroyd
Apr 12 22:57
something about there being a trait with associated type?
Zakarum
@omni-viral
Apr 12 22:57
Something about haveing field of associated type
This makes type invariant over generic parameter
i.e. you can't coerce to same type but with paramter type with lesser lifetime
Bar<&'a T> -> Bar<&'b T> is not allowed even if 'a: 'b
Now since you can't coerce ArrayVec<[&'a mut T]> to ArrayVec<[&'b mut T]> you can't borrow from it as &'b mut [&'b mut T].
You can't borrow from it as &'b mut [&'a mut T] and then coerce stlice too
And you can't borrow as &'a mut [&'a mut T] because ArrayVec does not live long anough.
David Holroyd
@dholroyd
Apr 12 23:09
    fn doit(&mut self, sock: &mio::net::UdpSocket) -> Result<usize, nix::Error> {
        let mut i = self.packet_bufs
            .iter_mut();
        let mut iovecs = [
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ],
        ];
        Self::doit2(sock, &mut self.sockaddrs[..], &mut self.lengths, &mut iovecs[..])
    }
^--- there, I 'fixed' it
so pretty!
Zakarum
@omni-viral
Apr 12 23:11
macro to rescue
David Holroyd
@dholroyd
Apr 12 23:15
    fn doit(&mut self, sock: &mio::net::UdpSocket) -> Result<usize, nix::Error> {
        let mut i = self.packet_bufs
            .iter_mut();
        let mut iovecs = array_macro::array![
            [ IoVec::from_mut_slice(i.next().unwrap().payload_mut()) ]; 8
        ];
        Self::doit2(sock, &mut self.sockaddrs[..], &mut self.lengths, &mut iovecs[..])
    }
thanks for your help @omni-viral !
Zakarum
@omni-viral
Apr 12 23:23
@dholroyd I found old closed issue here bluss/arrayvec#96
Closed without resolution though
However I submitted similar issue to smallvec
David Holroyd
@dholroyd
Apr 12 23:27
..subscribed