Ogeon/rustful

jsonrpc for rustful

Closed this issue · 12 comments

I am new at rust and I am having ownership problems.

My json rps functions look this this,

fn cmd_hello(data: &Database, context: Context, cmd: Value) -> Result<Option<String>, Error>{} 

I copied the the code from the todo.rs to parse the context.body
let todo: Value = try!(serde_json::from_reader(context.body).map_err(|_| Error::ParseError));

then I get the command
let cmd = todo.get("cmd").unwrap().as_str().unwrap()

then send it to the correct handler
match cmd {
"hello" => cmd_hello(data, context, todo),
....
_ => Err(Error::CmdNotFound)
}

I am getting ownership errors on both context and todo.
I understand context.body is where I borrow context and todo.get() is where I borrow todo. BUt knowing where the problem is does not help.

Could some one please post a small snippet of code that I could use solve this problem.
I plan to use a HashMap later but for now just getting it working is more important.

Ogeon commented

Hi! I would guess that the reason for your ownership problems is that cmd borrows from todo (as_str returns &str), so you can't move todo into cmd_hello until cmd is out of scope. A simple solution would be to convert the command to a String by doing something like this:

let cmd = todo.get("cmd").unwrap().as_str().unwrap().to_owned();
// or
let cmd: String = todo.get("cmd").unwrap().as_str().unwrap().into();
// or
let cmd = String::from(todo.get("cmd").unwrap().as_str().unwrap());
// or
let cmd = todo.get("cmd").unwrap().as_str().unwrap().to_string();

Which one you pick is a matter of taste. 😄 I hope that helps!

Please let me confirm what you said.
.to_owned() makes cmd the owner
cmd: String = ....into() move it into cmd
String::from() pulls ownership from todo
.to_string() does it just convert or does it take ownership?

Next, if "cmd" is not found in todo.get(), None is returned, will it get a crash if None is returned?

Ogeon commented

They will all have the exact same result, just through slightly different paths.

  • to_owned() uses the ToOwned trait to turn a value into its owned counterpart. String is the owned counterpart to &str.
  • into() will use the Into trait to make a conversion. The let cmd: String part gives the compiler enough hints to know that it should be converted into a String.
  • String::from(...) is exactly the same as the above variant, but "from the other end" using the From trait. It's basically "make a String from this".
  • to_string() converts anything that can be printed (like in println!("{}", x)) to a String. Since &str can be printed, it can be converted to a String.

Next, if "cmd" is not found in todo.get(), None is returned, will it get a crash if None is returned?

Yes, and that's not particularly desirable in a web server. What you can do, since both of the unwrap cases are for Option, is sending a response to the user when something is not right:

let cmd = if let Some(cmd) = todo.get("cmd")and_then(|v| v.as_str()).and_then(String::from) {
    cmd // just returns it to the variable
} else {
    return Err(...); // something about "cmd" being a mandatory string
};

The .and_then(String::from) is just the same thing as doing String::from(...) on the content of the Option, but without an extra closure/lambda. We are just passing the function in there instead.

Ogeon commented

(Updated with some links)

Thank you for helping. I have programmed in many languages and like the concepts of rust but getting my head around the syntax of rust is "interesting"

let cmd = if let Some(cmd) = todo.get("cmd")and_then(|v| v.as_str()).and_then(String::from) {
cmd // just returns it to the variable

the if let Some(cmd) does the same thing as unwrap() ? it is needed because an Option is passed back.
todo.get("cmd") finds the object in todo.
and_then() returns None is "cmd" is not found or calls the |v| v.as_str() converting it &str that returns a Option.
the next and_then converts Option<&str> to Option.
I an getting an error here. .and_then(String::from) it is Option but it expects String. I understand that "_" is being passed to String::from

Ogeon commented

Sorry, .and_then(String::from) should be .map(String::from). I suggest that you read about the methods of Option in the documentation, where both of these are explained.

if let <pattern> = <expression> { is like match, but for only one case, so if let Some(x) = ... { x } else { 0 } will result in x if it gets Some or 0 if it gets None. You could say that it's like unwrap, but you can choose what to do in each case.

So the difference between map and and_then is map() just executes the function and and_then checks for None then executes the function.

I have copied the code in question here. I am still getting and error on passing context to cmd_hello(tnvdata, context, todo). the from_reader(context.body) takes some ownership.
I understand that when I read the context.body I take ownership of part of context.
I was thinking I could copy "todo" to something else but then how do I release the ownership of that todo taken from context.

so I would
take
copy
release
that is what I wanted to do in rdBody()

fn cmd_hello(tnvdata: &Database, context: Context, cmd: Value) -> Result<Option, Error> {
let mut rslt = json!({"salt": "1023456", "nonce":"12345"}); //just temp
Ok(serde_json::to_string(&rslt))
}

fn rdBody(context: Context) -> Result<Option, Error> {
let todo: Value = try!(serde_json::from_reader(context.body).map_err(|_| Error::ParseError));
println!("{:?}", todo.as_object() );
Ok(Some(todo))
}

fn tnv_post(tnvdata: &Database, context: Context) -> Result<Option, Error> {
// let b = rdBody(context);
let todo: Value = try!(serde_json::from_reader(context.body).map_err(|_| Error::ParseError));
println!("{:?}", todo);
// let obj = todo.as_object().unwrap();
let cmd = if let Some(cmd) = todo.get("cmd").and_then(|v| v.as_str()).map(String::from) {
cmd // just returns it to the variable
} else {
return Err(Error::CmdNotFound); // something about "cmd" being a mandatory string
};
println!("{:?}", cmd);
let hello = "hello".to_string();
match cmd {
hello => cmd_hello(tnvdata, context, todo),
// "login" => cmd_login(tnvdata, context, todo),
// "pass" => cmd_pass(tnvdata, context, todo),
// "regdata" => cmd_regdata(tnvdata, context, todo),
_ => Err(Error::CmdNotFound)
}

// let mut rslt = json!({"salt": "1023456", "nonce":"12345"});
// Ok(Some(serde_json::to_string(&rslt).unwrap()))
}

Ogeon commented

So the difference between map and and_then is map() just executes the function and and_then checks for None then executes the function.

Nah, and_then is like map, but expects another Option to be returned instead of just the value. That's why it's good for chaining operations that return Option.

the from_reader(context.body) takes some ownership.

I didn't notice that. You don't have to move body out of context, you can use a reference: from_reader(&mut context.body)

I tried &context.body and it failed but &mut context.body works, why?

Ogeon commented

Because reading is an operation that changes the body. It consumes its content, so it has to be mutable.

That is because context.body is a reader stream. Ah!

I have only been programming rust for about a week. Talk about jumping into the deep end.

My final goal is to have a mongodb backed jsonrpc like server. Right now I am working on the login and session part. I am using tweetnacl as the javascript library. What is the best crypto library for rust that supports ed25519?

Thanks for all the help.

Ogeon commented

No problem! Feel free to ask again if you have any questions about Rustful.

Deep end or not, a clear goal and some determination will you far. It's at least a learning experience!

Unfortunately I don't really know much about the crypto ecosystem. Your best bet is to search crates.io and ask in the user forum or on Reddit.

Good luck!