So I have gone through 90% of the tutorial on Rust and I think I mostly have a grasp on the syntax. I'm attempting to start writing code with it I'm currently using the rustc_serialize library to parse JSON from stdin and I'm not getting the results I expect. I have the following JSON file called message.txt the following content:
{"text": "hello world"}
Here is the Rust code to accept stdin and parse out the text field:
extern crate rustc_serialize;
use std::io::{self, Read};
use rustc_serialize::json::Json;
fn main() {
// provide a buffer for stdin
let mut buffer = String::new();
let _ = io::stdin().read_to_string(&mut buffer);
// parse the json
let message = match Json::from_str(&mut buffer) {
Ok(m) => m,
Err(_) => panic!("Stdin provided invalid JSON")
};
// get the message object and "text" field string
let message_object = message.as_object().unwrap();
let message_string = message_object.get("text").unwrap();
println!("{}", message_string);
println!("{}", &message_string.to_string()[0..4]);
}
The following code outputs:
"Hello World"
"Hel
I'm currently outputting the byte slice to make sure the quote wasn't something that was added by print. According to the docs message_string shouldn't have quotes around it.
If I print out the data using the example from the documentation then it prints the value of "text" without quotes:
for (key, value) in message_object.iter() {
println!("{}: {}", key, match *value {
Json::U64(v) => format!("{} (u64)", v),
Json::String(ref v) => format!("{} (string)", v),
_ => format!("other")
});
}
Output:
text: hello world (string)
I'm a newbie to Rust so I probably just don't understand the string manipulation parts of Rust all that well.
The problem is that message_string isn't what you think it is. I discovered that when I tried to use len on the "string", which didn't work (I assume that's why you have a to_string when you are slicing). Let's make the compiler tell us what it is:
let () = message_string;
Has the error:
error: mismatched types:
expected `&rustc_serialize::json::Json`,
found `()`
It's a Json! We need to convert that enumerated type into a string-like thing:
let message_object = message.as_object().unwrap();
let message_json = message_object.get("text").unwrap();
let message_string = message_json.as_string().unwrap();
Ultimately, I'd argue that Display (which allows the {} format string) should not have been implemented for this type, as Display means format in an end-user-focused manner. It's probably too late to change that decision now though.
I know that unwrap is great for quick prototyping, but I'd be remiss in not showing a slightly more idiomatic way of doing this:
fn main() {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer).expect("Could not read from stdin");
let message = Json::from_str(&mut buffer).expect("Stdin provided invalid JSON");
let message_string = message.as_object().and_then(|obj| {
obj.get("text").and_then(|json| {
json.as_string()
})
}).expect("The `text` key was missing or not a string");
println!("{}", message_string);
}
Ignoring the Result from read_to_string is worse than panicking. ^_^
Related
The idea here is simple but I have tried three different ways with different errors each time: read in a string as an argument, but if the string is invalid or the string isn't provided, use a default.
Can this be done using Result to detect a valid string or a panic?
The basic structure I expect:
use std::env;
use std::io;
fn main() {
let args: Vec<String> = args().collect();
let word: Result<String, Error> = &args[1].expect("Valid string");
let word: String = match word {
Ok(word) = word,
Err(_) = "World",
}
println!("Hello, {}", word);
}
So, there are a lot of issues in your code.
First and foremost, in a match statement, you do not use =, you use =>.
Additionally, your match statement returns something, which makes it not an executing block, but rather a returning block (those are not the official terms). That means that your blocks result is bound to a variable. Any such returning block must end with a semicolon.
So your match statement would become:
let word: String = match word {
Ok(word) => word,
Err(_) => ...,
};
Next, when you do use std::env, you do not import all of the functions from it into your namespace. All you do is that you create an alias, so that the compiler turns env::<something> intostd::env::<something> automatically.
Therefore, this needs to be changed:
let args: Vec<String> = env::args().collect();
The same problem exists in your next line. What is Error? Well, what you actually mean is io::Error, that is also not imported due to the same reasons stated above. You might be wondering now, how Result does not need to be imported. Well, it is because the Rust Team has decided on a certain set of functions and struct, which are automatically imported into every project. Error is not one of them.
let word: Result<String, io::Error> = ...;
The next part is wrong twice (or even thrice).
First of all, the operation [x] does not return a Result, it returns the value and panics if it is out-of-bounds.
Now, even if it was a result, this line would still be wrong. Why? Because you expect(...) the result. That would turn any Result into its value.
Now, what you are looking for is the .get(index) operation. It tries to get a value and if it fails, it returns None, so it returns an option. What is an option? It is like a result, but there is no error value. It must be noted that get() returns the option filled with a reference to the string.
The line should look something like this:
let word: Option<&String> = args.get(1);
Now you have two options to handle default values, but before we come to that, I need to tell you why your error value is wrong.
In Rust, there are two kinds of Strings.
There is ´&str`, which you can create like this:
let a: &str = "Hello, World!";
These are immutable and non-borrowed strings stored on the stack. So you cannot just create a new one with arbitary values on the fly.
On the other hand, we have mutable and heap-allocated Strings.
let mut a: String = String::new();
a.push_str("Hello, World!");
// Or...
let b: String = String::from("Hello, World");
You store your arguments as a String, but in your match statement, you try to return a &str.
So, there are two ways to handle your error:
let word: Option<&String> = args.get(1);
let word: String = match word {
Some(word) => word.to_string(),
None => String::from("World"),
};
If you do not want to allocate that second string, you can also use
let word: Option<&String> = args.get(1);
let word: &str = match word {
Some(word) => word.as_str(),
None => "World",
};
The second option, unwrap_or
let args: Vec<String> = env::args().collect();
let default = &String::from("World");
let word: &String = args.get(1).unwrap_or(default);
println!("Hello, {}", word);
is a bit uglier, as it requires you to bind the default value to a variable. This will do what your match statement above does, but it's a bit prettier.
This works too:
let word: &str = args.get(1).unwrap_or(default);
So this is my favourite version of your program above:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let default = &String::from("World");
let word: &str = args.get(1).unwrap_or(default);
println!("Hello, {}", word);
}
But this one works too:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let word: Option<&String> = args.get(0);
let word: &str = match word {
Some(word) => word.as_str(),
None => "World",
};
println!("Hello, {}", word);
}
I am new to Rust and trying to figure out how to get into a workflow of discovering the structure of this AST returned by syn.
[package]
name = "rust-ast"
version = "0.1.0"
authors = ["Foo Bar <foo#bar.com>"]
edition = "2018"
[dependencies]
syn = { version = "1.0.89", features = ["full", "printing", "visit", "extra-traits"] }
That is the Cargo.toml, and here is the main file:
use syn;
use std::env;
use std::fs::File;
use std::io::Read;
use std::process;
fn main() {
let mut args = env::args();
let _ = args.next(); // executable name
let filename = match (args.next(), args.next()) {
(Some(filename), None) => filename,
_ => {
eprintln!("Usage: dump-syntax path/to/filename.rs");
process::exit(1);
}
};
let mut file = File::open(&filename).expect("Unable to open file");
let mut src = String::new();
file.read_to_string(&mut src).expect("Unable to read file");
let syntax = syn::parse_file(&src).expect("Unable to parse file");
let mut str = String::from("");
for item in syntax.items {
match item {
syn::Item::Use(x) => {
match x.tree {
syn::path::Path { .. } => {
println!("{:#?}", x);
},
}
str.push_str("load");
},
_ => println!("Skip")
}
}
// let iterator = syntax.iter();
// for val in iterator {
// println!("Got: {:#?}", val);
// }
}
I was able to earlier print out the x, which showed:
However, I am getting this error now:
this expression has type `syn::UseTree`
expected enum `syn::UseTree`, found struct `syn::Path`
First of all, how do I discover the API in VSCode? I have enabled the rust plugin so I can click through some definitions, but I look at that terminal output of the AST "types", and then I try to backward figure out what the type is in VSCode. Usually I resort to looking it up on the docs page, but any tips on how to figure out what the API should be would help teach me to fish. But for this particular question, what am I doing wrong? I am simply trying to destructure the AST as much as possible down to the leaves, to get more familiar with Rust (I am just beginning).
But for this particular question, what am I doing wrong?
x.tree is of type UseTree which is an enum:
pub enum UseTree {
Path(UsePath),
Name(UseName),
... other variants snipped ...
}
Therefore you need to match on the Path variant of UseTree:
match x.tree {
UseTree::Path { .. } => { ... },
_ => todo!(),
}
First of all, how do I discover the API in VSCode?
I don't use VS code but I find clicking through works well for Rust and the stdlib and less well for macro heavy libraries such as syn. Fortunately the documentation for syn is really good.
am simply trying to destructure the AST as much as possible down to the leaves, to get more familiar with Rust (I am just beginning).
syn can be quite complex and so I wouldn't advise this as an approach to learning the language.
playground
use serde_json::json; // 1.0.66
use std::str;
fn main() {
let input = "{\"a\": \"b\\u001fc\"}";
let bytes = input.as_bytes();
let json: serde_json::Value = serde_json::from_slice(bytes).unwrap();
for (_k, v) in json.as_object().unwrap() {
let vec = serde_json::to_vec(v).unwrap();
let utf8_str = str::from_utf8(&vec).unwrap();
println!("value: {}", v);
println!("utf8_str: {}", utf8_str);
println!("bytes: {:?}", vec);
}
}
How can the value of object key "a" be transformed into the following string?
b\u{1f}c
I've tried with serde_json and str::from_utf8, but I always get "b\u001fc" as the result. The escaped character sequence is not interpreted correctly. How this can be solved?
The problem is this line:
let vec = serde_json::to_vec(v).unwrap();
From the serde_json docs on to_vec():
Serialize the given data structure as a JSON byte vector.
You are deserializing from JSON, getting the values of the object, serializing them back to JSON and printing that. You don't want to serialize back to JSON, you want to print the "raw" string, so something like this does what you want:
fn main() {
let input = "{\"a\": \"b\\u001fc\"}";
let bytes = input.as_bytes();
let json: serde_json::Value = serde_json::from_slice(bytes).unwrap();
for (_k, v) in json.as_object().unwrap() {
let string = v.as_str().unwrap();
println!("bytes: {:?}", string);
}
}
Playground
I think things are closer to working than you think. Your problem is not that the escape sequence isn't being interpreted correctly, but rather that serde_json::to_vec(v) essentially re-encodes v (which is serde_json::value::Value::String) into a vector of JSON-encoded bytes. This means that it picks up the surrounding quote characters (byte 34) and turns the escape sequence into a literal ['\\', 'u', ...] — because that's how it would look in JSON.
If you want to get the string value out, you can do this:
for (_k, v) in json.as_object().unwrap() {
if let serde_json::value::Value::String(s) = v {
println!("{:?}", s);
}
}
This prints "b\u{1f}c", the Rust string you want.
I was wondering how to convert a styled string into a vector. Say I had a String with the value:
"[x, y]"
-how could I turn it into a vector that has x as the first object and y as the second object?
Thanks!
Sure, but the elements can't be references. As mentioned by #prog-fh that isn't possible in rust since once compiled, variable names may not be stored and the compiler may have even removed some during optimizations.
You can however do something more similar to python's ast.literal_eval using serde with Rust Object Notation (RON, a type of serialization that was made to resemble rust data structures). It isn't perfect, but it is an option. It does however require you know what types you are trying to parse.
use ron::from_str;
let input = "[37.6, 24.3, 89.023]";
let parsed: Vec<f32> = from_str(input).unwrap();
On the other hand if #mcarton is correct and you want something like vec!["x", "y"], you could manually parse it like so:
fn parse(input: &str) -> Option<Vec<String>> {
let mut part = String::new();
let mut collected = Vec::new();
let mut char_iter = input.chars();
if char_iter.next() != Some('[') {
return None
}
loop {
match char_iter.next()? {
']' => {
collected.push(part);
return Some(collected)
}
',' | ' ' => {
if !part.is_empty() {
collected.push(part);
part = String::new();
}
}
x => part.push(x),
}
}
}
println!("{:?}", parse("[a, b, foo]"));
Or you could also use a regex to break it up instead, but you can look into how that works yourself.
I'm trying to use the Iron framework to build a simple backend in Rust. This handler is just supposed to return the content of a certain file and I can get it to work properly with unwrap() but I want to try to do proper error handling. This is how I would imagine it would look like:
fn get_content(res: &mut Request) -> IronResult<Response> {
let mut id = String::new();
res.body.read_to_string(&mut id).unwrap();
let file_path_string = &("../content/".to_string() + &id + ".rdt");
// TODO: Error handling
match File::open(file_path_string) {
Ok(f) => {
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Err(err) => Err(Response::with(((status::InternalServerError), "File not found")))
};
}
This throws the error not all control paths return a value [E0269], which is fine. But if I add a response after the match part:
match File::open(file_path_string) {
Ok(f) => {
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Err(err) => Err(Response::with(((status::InternalServerError), "File not found")))
};
Err(Response::with(((status::InternalServerError), "File not found")))
I instead get the error message:
expected `iron::error::IronError`,
found `iron::response::Response`
(expected struct `iron::error::IronError`,
found struct `iron::response::Response`) [E0308]
src/main.rs:95
Err(Response::with(((status::InternalServerError), "File not found")))
I think the problem is the collision between Rust Err and Iron Err? I'm not sure though. And I have not done much web development (or Rust for that matter) in the past so any feedback on the code is also appreciated!
UPDATE: I think this is more "The Rust Way" to do it? But I'm not sure
fn get_content(res: &mut Request) -> IronResult<Response> {
let mut id = String::new();
res.body.read_to_string(&mut id).unwrap();
let file_path_string = &("../content/".to_string() + &id + ".rdt");
// TODO: Error handling
let f;
match File::open(file_path_string) {
Ok(file) => f = file,
Err(err) => Err(HttpError::Io(err))
};
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Having the code inside the error handling seems weird as read_to_string also needs to be taken care of and that would create a nested mess of error handling? However, these matching arms are obviously of incompatible types so it won't work... any suggestions?
An Ok() takes an Response, but an Err() takes an IronError.
Hence your call Err(...) is not valid when ... is a Response!
How to correct it? Well the first step is, you must create an IronError to send back. I believe (not familiar with Iron) that Iron will automatically an appropriate error code and that it's not your job to do that. In the documentation we find one key type implementing IronError:
pub enum HttpError {
Method,
Uri(ParseError),
Version,
Header,
TooLarge,
Status,
Io(Error),
Ssl(Box<Error + 'static + Send + Sync>),
Http2(HttpError),
Utf8(Utf8Error),
// some variants omitted
}
I can't see one which allows for an arbitrary string like "file not found". However, your use case is one of an IO failure, right? So it would make sense to use HttpError::Io with the std::IoError that you got back from File::open():
match File::open(file_path_string) {
Ok(f) => {
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Err(err) => Err(HttpError::Io(err))
};
By the way, it also fixes your "TODO: error handling"! How beautiful!
(Code untested, please feel free to edit if compilation fails)