Why does the compiler infer the type here to be the unit type?

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.

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

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.