Was playing around with tide and found that the following compiled:
use tide::{error::ResultExt, response, App, Context, EndpointResult};
async fn echo_json(mut ctx: Context<()>) -> EndpointResult {
let msg = ctx.body_json().await.client_err()?;
println!("JSON: {:?}", msg);
Ok(response::json(msg))
}
fn main() {
let mut app = App::new();
app.at("/json").post(echo_json);
app.serve("127.0.0.1:8000").unwrap();
}
Running with
cargo +beta run
It seems the only value that is allowed is null
.
curl -v -d 'null' "http://localhost:8000/json"
Which means that the compiler inferred the type of msg
to be ()
. Why does it infer that? I would have thought it would not be able to infer it or at least refuse to infer it.
2 Likes
You need to put a different type in Context
to support them. If you have Context<()>
, theb you will always get a ()
from it.
Why would it be that type? The signature for body_json()
is
pub async fn body_json<'_, T: DeserializeOwned>(&'_ mut self) -> Result<T>
Does a generic T
default to ()
if it "works". I know it is possible to do something like
use serde::{Deserialize, Serialize};
use tide::{error::ResultExt, response, App, Context, EndpointResult};
#[derive(Clone, Debug, Deserialize, Serialize)]
struct Message {
author: Option<String>,
contents: String,
}
async fn echo_json(mut ctx: Context<()>) -> EndpointResult {
let msg: Message = ctx.body_json().await.client_err()?;
println!("JSON: {:?}", msg);
Ok(response::json(msg))
}
fn main() {
let mut app = App::new();
app.at("/json").post(echo_json);
app.serve("127.0.0.1:8000").unwrap();
}
And then
curl -v -d '{"contents": "blah"}' "http://localhost:8000/json"
This works as expected, but obviously the type of msg
is not inferred. If you replace
let msg: Message = ctx.body_json().await.client_err()?;
with
let msg = ctx.body_json().await.client_err()?;
it infers the type as ()
.
Oh, looks like I got some things mixed up. I'm not sure what is going on.
Okay was able to replicate outside of the tide package. The following infers msg
to be ()
.
use async_std::task;
async fn parse<T: serde::de::DeserializeOwned>(s: String) -> std::io::Result<T> {
Ok(serde_json::from_str(&s).map_err(|_| std::io::ErrorKind::InvalidData)?)
}
async fn body_json(s: String) -> std::io::Result<String> {
let msg = parse(s).await?;
println!("JSON: {:?}", msg);
Ok(serde_json::to_string(&msg ).map_err(|_| std::io::ErrorKind::InvalidData)?)
}
fn main() {
task::block_on(body_json("null".to_string()));
}
If I just change
let msg = parse(s).await?;
to
let msg = parse(s).await.unwrap();
Then it doesn't compile.
alice
November 5, 2019, 2:46pm
6
Weird. This also compiles:
async fn body_json(s: String) -> std::io::Result<String> {
let msg = match parse(s).await {
Ok(ok) => ok,
Err(err) => return Err(err),
};
println!("JSON: {:?}", msg);
Ok(serde_json::to_string(&msg ).map_err(|_| std::io::ErrorKind::InvalidData)?)
}
Something's definitely wrong here. Even this compiles!
fn parse<T: Default>() -> Option<T> {
Some(T::default())
}
fn main() {
let msg = match parse() {
Some(ok) => ok,
None => return,
};
println!("{:?}", msg);
}
This still compiles with msg: i32
, so I have no idea why it thinks that msg should be inferred as ()
. If I use println!("{}", msg);
then it complains about ()
not implementing Display
.
An even further simplified example also compiles:
fn parse()<T>() -> Option<T> {
println!("type used: {}", std::any::type_name::<T>());
None
}
fn main() {
let _v = match parse() {
Some(v) => v,
None => return,
};
}
If I remove the match
statement, it stops working. This can work with _v
being any type, and we can observe that it chooses ()
.
Maybe this has something to do with how return
expressions have their type inferred?
If you enable #![feature(never_type)]
, then it will infer that type instead (and complain about !
not implementing the traits required, if any):
#![feature(never_type)]
fn parse<T: Default>() -> Option<T> {
Some(T::default())
}
fn main() {
let _v = match parse() {
Some(v) => v,
None => return,
};
}
error[E0277]: the trait bound `!: std::default::Default` is not satisfied
--> src/main.rs:7:20
|
2 | fn parse<T: Default>() -> Option<T> {
| ----- ------- required by this bound in `parse`
...
7 | let _v = match parse() {
| ^^^^^ the trait `std::default::Default` is not implemented for `!`
|
= note: the trait is implemented for `()`. Possibly this error has been caused by changes to Rust's type-inference algorithm (see: https://github.com/rust-lang/rust/issues/48950 for more info). Consider whether you meant to use the type `()` here instead.
4 Likes
Yes, this seems to be it! How did I miss that. That is why ?
and match
work, but unwrap
doesn't. Currently, return
and other constructs that should return !
default to ()
, Rust then assumes both branches have the same type, and then propagates that information to parse
.
8 Likes
system
Closed
February 4, 2020, 1:17am
9
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.