"asking await" syntax proposal

It's intended as a new await syntax proposal, however, I'm not certain on 100% that it's really new considering the enormous amount of the previous discussion. At least brief search over summaries on some threads didn't give me any occurrences, so if it's duplication I just want to draw again attention on it, and I forward apologize for an accidental steal of someone's underappreciated idea.


Background and introduction

I wasn't a very big fan of using await keyword from the very beginning of a whole await-syntax discussion because I've had a feeling that there's simply no place for it. However, I did some syntax experiments that still contain it and recently I've found a solution that either looks reasonable for me.

It's also postfix, it's consistent with many other Rust language constructs, it doesn't confuse with field access or method invoking, don't suffer from wrong associativity problems, and it doesn't make any compromises on code readability or self-documentation.

The grammar is:

EXPR await?

Why does it even make a sense?

Because there's a lot of good arguments behind this syntax!

  1. It looks very pleasant aesthetically

  2. It looks familiarly with other constructs

    Consider how references syntax looks: &Foo, &mut Foo, &'a mut Foo
    Consider how function traits syntax looks: Fn() -> ..., FnOnce() -> ..., FnMut() -> ...
    Consider how memory wrappers names looks: Cell<T>, RefCell<T>, UnsafeCell<T>

    It's clear that await? is nothing but a more concrete case of ?

  3. It looks exactly like all keyword should look

    Unlike officially proposed syntax it doesn't look like a domain item inside of a method call chain:

    2019-05-25-111318

    vs

    2019-05-25-111509

    and like all other keywords it remains space-separated from domain.

  4. It still has proper associativity

    Even if there's whitespace before await it still remains connected more tightly to the preceding expression because the question mark is stronger as we are accustomed that it's always applied at the end of an expression.

    2019-05-25-211120

  5. It could be extended only in a proper manner

    In the future, it's possible to add other postfix operators, but nobody will ask to allow existed keywords in postfix position, e.g. .match, .for, .if, etc.

  6. It has a consistent formatting style

    .await is readable either at the end of a line and on a new line, while await? is only readable at the end of a line; if it becomes too long, then a single solution is to create a temporary binding.


Similarity with error handling

It's obvious that the semantics of ? operator would be completely changed and if previously it was strictly associated with error handling and Try trait, now it's more like that.

Now error handling is highly tied with awaiting futures:

But that also a very big advantage of this syntax!

  1. It explicitly says that await is fallible operation and now if some code will not execute then it's enough to spot all occurrences of ? to find places where interruption might occur.
  2. The difference between await and ? is irrelevant in most of the times as mostly when we read await?? we only interested in a previous expression to result in a valid value

And more real-world examples

Below is code from await-syntax repository (also the source for code on images) rewritten with await?

// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#![feature(async_await, await_macro, futures_api)]
#![deny(warnings)]

use failure::{bail, format_err, Error, ResultExt};
use fidl::endpoints;
use fidl_fuchsia_wlan_device_service::{
    self as wlan_service, DeviceServiceMarker, DeviceServiceProxy,
};
use fidl_fuchsia_wlan_minstrel::Peer;
use fidl_fuchsia_wlan_sme::{
    self as fidl_sme, ConnectResultCode, ConnectTransactionEvent, ScanTransactionEvent,
};
use fuchsia_app::client::connect_to_service;
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
use futures::prelude::*;
use std::fmt;
use std::str::FromStr;
use structopt::StructOpt;

mod opts;
use crate::opts::*;

const SCAN_REQUEST_TIMEOUT_SEC: u8 = 10;

type WlanSvc = DeviceServiceProxy;

fn main() -> Result<(), Error> {
    let opt = Opt::from_args();
    println!("{:?}", opt);

    let mut exec = fasync::Executor::new().context("error creating event loop")?;
    let wlan_svc = connect_to_service::<DeviceServiceMarker>()
        .context("failed to connect to device service")?;

    let fut = async {
        match opt {
            Opt::Phy(cmd) => do_phy(cmd, wlan_svc) await?,
            Opt::Iface(cmd) => do_iface(cmd, wlan_svc)) await?,
            Opt::Client(cmd) => do_client(cmd, wlan_svc) await?,
            Opt::Ap(cmd) => do_ap(cmd, wlan_svc) await?,
            Opt::Mesh(cmd) => do_mesh(cmd, wlan_svc) await?,
        }
    };
    exec.run_singlethreaded(fut)
}

async fn do_phy(cmd: opts::PhyCmd, wlan_svc: WlanSvc) -> Result<(), Error> {
    match cmd {
        opts::PhyCmd::List => {
            // TODO(tkilbourn): add timeouts to prevent hanging commands
            let response = wlan_svc.list_phys() await?
                .context("error getting response")?;
            println!("response: {:?}", response);
        }
        opts::PhyCmd::Query { phy_id } => {
            let mut req = wlan_service::QueryPhyRequest { phy_id };
            let response = wlan_svc.query_phy(&mut req) await?
                .context("error querying phy")?;
            println!("response: {:?}", response);
        }
    }
    Ok(())
}

async fn do_iface(cmd: opts::IfaceCmd, wlan_svc: WlanSvc) -> Result<(), Error> {
    match cmd {
        opts::IfaceCmd::New { phy_id, role } => {
            let mut req = wlan_service::CreateIfaceRequest { phy_id: phy_id, role: role.into() };

            let response = wlan_svc.create_iface(&mut req) await?
                .context("error getting response")?;
            println!("response: {:?}", response);
        }
        opts::IfaceCmd::Delete { phy_id, iface_id } => {
            let mut req = wlan_service::DestroyIfaceRequest { phy_id: phy_id, iface_id: iface_id };

            let response = wlan_svc.destroy_iface(&mut req) await?
                .context("error destroying iface")?;
            match zx::Status::ok(response) {
                Ok(()) => println!("destroyed iface {:?}", iface_id),
                Err(s) => println!("error destroying iface: {:?}", s),
            }
        }
        opts::IfaceCmd::List => {
            let response = wlan_svc.list_ifaces() await?
                .context("error getting response")?;
            println!("response: {:?}", response);
        }
        opts::IfaceCmd::Query { iface_id } => {
            let response = wlan_svc.query_iface(iface_id) await?
                .context("error querying iface")?;
            println!("response: {:?}", response);
        }
        opts::IfaceCmd::Stats { iface_id } => {
            let ids = get_iface_ids(wlan_svc.clone(), iface_id) await??;

            for iface_id in ids {
                let (status, resp) = wlan_svc.get_iface_stats(iface_id) await?
                    .context("error getting stats for iface")?;
                match status {
                    zx::sys::ZX_OK => {
                        match resp {
                            // TODO(eyw): Implement fmt::Display
                            Some(r) => println!("Iface {}: {:#?}", iface_id, r),
                            None => println!("Iface {} returns empty stats resonse", iface_id),
                        }
                    }
                    status => println!("error getting stats for Iface {}: {}", iface_id, status),
                }
            }
        }
        opts::IfaceCmd::Minstrel(cmd) => match cmd {
            opts::MinstrelCmd::List { iface_id } => {
                let ids = get_iface_ids(wlan_svc.clone(), iface_id) await??;
                for id in ids {
                    if let Ok(peers) = list_minstrel_peers(wlan_svc.clone(), id) await? {
                        if peers.is_empty() {
                            continue;
                        }
                        println!("iface {} has {} peers:", id, peers.len());
                        for peer in peers {
                            println!("{}", peer);
                        }
                    }
                }
            }
            opts::MinstrelCmd::Show { iface_id, peer_addr } => {
                let peer_addr = match peer_addr {
                    Some(s) => Some(s.parse()?),
                    None => None,
                };
                let ids = get_iface_ids(wlan_svc.clone(), iface_id) await??;
                for id in ids {
                    if let Err(e) =
                        show_minstrel_peer_for_iface(wlan_svc.clone(), id, peer_addr) await?
                    {
                        println!(
                            "querying peer(s) {} on iface {} returned an error: {}",
                            peer_addr.unwrap_or(MacAddr([0; 6])),
                            id,
                            e
                        );
                    }
                }
            }
        },
    }
    Ok(())
}

async fn do_client(cmd: opts::ClientCmd, wlan_svc: WlanSvc) -> Result<(), Error> {
    match cmd {
        opts::ClientCmd::Scan { iface_id, scan_type } => {
            let sme = get_client_sme(wlan_svc, iface_id) await??;
            let (local, remote) = endpoints::create_proxy()?;
            let mut req = fidl_sme::ScanRequest {
                timeout: SCAN_REQUEST_TIMEOUT_SEC,
                scan_type: scan_type.into(),
            };
            sme.scan(&mut req, remote).context("error sending scan request")?;
            handle_scan_transaction(local) await?
        }
        opts::ClientCmd::Connect { iface_id, ssid, password, phy, cbw, scan_type } => {
            let sme = get_client_sme(wlan_svc, iface_id) await??;
            let (local, remote) = endpoints::create_proxy()?;
            let mut req = fidl_sme::ConnectRequest {
                ssid: ssid.as_bytes().to_vec(),
                password: password.unwrap_or(String::new()).as_bytes().to_vec(),
                radio_cfg: fidl_sme::RadioConfig {
                    override_phy: phy.is_some(),
                    phy: phy.unwrap_or(PhyArg::Vht).into(),
                    override_cbw: cbw.is_some(),
                    cbw: cbw.unwrap_or(CbwArg::Cbw80).into(),
                    override_primary_chan: false,
                    primary_chan: 0,
                },
                scan_type: scan_type.into(),
            };
            sme.connect(&mut req, Some(remote)).context("error sending connect request")?;
            handle_connect_transaction(local) await?
        }
        opts::ClientCmd::Disconnect { iface_id } => {
            let sme = get_client_sme(wlan_svc, iface_id) await??;
            sme.disconnect() await?
                .map_err(|e| format_err!("error sending disconnect request: {}", e))
        }
        opts::ClientCmd::Status { iface_id } => {
            let sme = get_client_sme(wlan_svc, iface_id) await??;
            let st = sme.status() await??;
            match st.connected_to {
                Some(bss) => {
                    println!(
                        "Connected to '{}' (bssid {})",
                        String::from_utf8_lossy(&bss.ssid),
                        MacAddr(bss.bssid)
                    );
                }
                None => println!("Not connected to a network"),
            }
            if !st.connecting_to_ssid.is_empty() {
                println!("Connecting to '{}'", String::from_utf8_lossy(&st.connecting_to_ssid));
            }
            Ok(())
        }
    }
}

async fn do_ap(cmd: opts::ApCmd, wlan_svc: WlanSvc) -> Result<(), Error> {
    match cmd {
        opts::ApCmd::Start { iface_id, ssid, password, channel } => {
            let sme = get_ap_sme(wlan_svc, iface_id) await??;
            let mut config = fidl_sme::ApConfig {
                ssid: ssid.as_bytes().to_vec(),
                password: password.map_or(vec![], |p| p.as_bytes().to_vec()),
                channel,
            };
            let r = sme.start(&mut config) await??;
            match r {
                fidl_sme::StartApResultCode::InvalidArguments => {
                    println!("{:?}: Channel {:?} is invalid", r, config.channel);
                }
                fidl_sme::StartApResultCode::DfsUnsupported => {
                    println!(
                        "{:?}: The specified role does not support DFS channel {:?}",
                        r, config.channel
                    );
                }
                _ => {
                    println!("{:?}", r);
                }
            }
        }
        opts::ApCmd::Stop { iface_id } => {
            let sme = get_ap_sme(wlan_svc, iface_id) await??;
            let r = sme.stop() await?;
            println!("{:?}", r);
        }
    }
    Ok(())
}

async fn do_mesh(cmd: opts::MeshCmd, wlan_svc: WlanSvc) -> Result<(), Error> {
    match cmd {
        opts::MeshCmd::Join { iface_id, mesh_id, channel } => {
            let sme = get_mesh_sme(wlan_svc, iface_id) await??;
            let mut config = fidl_sme::MeshConfig { mesh_id: mesh_id.as_bytes().to_vec(), channel };
            let r = sme.join(&mut config) await??;
            match r {
                fidl_sme::JoinMeshResultCode::InvalidArguments => {
                    println!("{:?}: Channel {:?} is invalid", r, config.channel);
                }
                fidl_sme::JoinMeshResultCode::DfsUnsupported => {
                    println!(
                        "{:?}: The specified role does not support DFS channel {:?}",
                        r, config.channel
                    );
                }
                _ => {
                    println!("{:?}", r);
                }
            }
        }
        opts::MeshCmd::Leave { iface_id } => {
            let sme = get_mesh_sme(wlan_svc, iface_id) await??;
            let r = sme.leave() await?;
            println!("{:?}", r);
        }
    }
    Ok(())
}

#[derive(Debug, Clone, Copy, PartialEq)]
struct MacAddr([u8; 6]);

impl fmt::Display for MacAddr {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(
            f,
            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
        )
    }
}

impl FromStr for MacAddr {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut bytes = [0; 6];
        let mut index = 0;

        for octet in s.split(|c| c == ':' || c == '-') {
            if index == 6 {
                bail!("Too many octets");
            }
            bytes[index] = u8::from_str_radix(octet, 16)?;
            index += 1;
        }

        if index != 6 {
            bail!("Too few octets");
        }
        Ok(MacAddr(bytes))
    }
}

async fn handle_scan_transaction(scan_txn: fidl_sme::ScanTransactionProxy) -> Result<(), Error> {
    let mut printed_header = false;
    let mut events = scan_txn.take_event_stream();
    while let Some(evt) = events.try_next() await?
        .context("failed to fetch all results before the channel was closed")?
    {
        match evt {
            ScanTransactionEvent::OnResult { aps } => {
                if !printed_header {
                    print_scan_header();
                    printed_header = true;
                }
                for ap in aps {
                    print_scan_result(ap);
                }
            }
            ScanTransactionEvent::OnFinished {} => break,
            ScanTransactionEvent::OnError { error } => {
                eprintln!("Error: {}", error.message);
                break;
            }
        }
    }
    Ok(())
}

fn print_scan_header() {
    println!("BSSID              dBm     Chan Protected SSID");
}

fn is_ascii(v: &Vec<u8>) -> bool {
    for val in v {
        if val > &0x7e {
            return false;
        }
    }
    return true;
}

fn is_printable_ascii(v: &Vec<u8>) -> bool {
    for val in v {
        if val < &0x20 || val > &0x7e {
            return false;
        }
    }
    return true;
}

fn print_scan_result(ess: fidl_sme::EssInfo) {
    let is_ascii = is_ascii(&ess.best_bss.ssid);
    let is_ascii_print = is_printable_ascii(&ess.best_bss.ssid);
    let is_utf8 = String::from_utf8(ess.best_bss.ssid.clone()).is_ok();
    let is_hex = !is_utf8 || (is_ascii && !is_ascii_print);

    let ssid_str;
    if is_hex {
        ssid_str = format!("({:X?})", &*ess.best_bss.ssid);
    } else {
        ssid_str = format!("\"{}\"", String::from_utf8_lossy(&ess.best_bss.ssid));
    }

    println!(
        "{} {:4} {:8} {:9} {}",
        MacAddr(ess.best_bss.bssid),
        ess.best_bss.rx_dbm,
        ess.best_bss.channel,
        if ess.best_bss.protected { "Y" } else { "N" },
        ssid_str
    );
}

async fn handle_connect_transaction(
    connect_txn: fidl_sme::ConnectTransactionProxy,
) -> Result<(), Error> {
    let mut events = connect_txn.take_event_stream();
    while let Some(evt) = events.try_next() await?
        .context("failed to receive connect result before the channel was closed")?
    {
        match evt {
            ConnectTransactionEvent::OnFinished { code } => {
                match code {
                    ConnectResultCode::Success => println!("Connected successfully"),
                    ConnectResultCode::Canceled => {
                        eprintln!("Connecting was canceled or superseded by another command")
                    }
                    ConnectResultCode::Failed => eprintln!("Failed to connect to network"),
                    ConnectResultCode::BadCredentials => {
                        eprintln!("Failed to connect to network; bad credentials")
                    }
                }
                break;
            }
        }
    }
    Ok(())
}

async fn get_client_sme(
    wlan_svc: WlanSvc,
    iface_id: u16,
) -> Result<fidl_sme::ClientSmeProxy, Error> {
    let (proxy, remote) = endpoints::create_proxy()?;
    let status = wlan_svc.get_client_sme(iface_id, remote) await?
        .context("error sending GetClientSme request")?;
    if status == zx::sys::ZX_OK {
        Ok(proxy)
    } else {
        Err(format_err!("Invalid interface id {}", iface_id))
    }
}

async fn get_ap_sme(wlan_svc: WlanSvc, iface_id: u16) -> Result<fidl_sme::ApSmeProxy, Error> {
    let (proxy, remote) = endpoints::create_proxy()?;
    let status = wlan_svc.get_ap_sme(iface_id, remote) await?
        .context("error sending GetApSme request")?;
    if status == zx::sys::ZX_OK {
        Ok(proxy)
    } else {
        Err(format_err!("Invalid interface id {}", iface_id))
    }
}

async fn get_mesh_sme(wlan_svc: WlanSvc, iface_id: u16) -> Result<fidl_sme::MeshSmeProxy, Error> {
    let (proxy, remote) = endpoints::create_proxy()?;
    let status = wlan_svc.get_mesh_sme(iface_id, remote) await?
        .context("error sending GetMeshSme request")?;
    if status == zx::sys::ZX_OK {
        Ok(proxy)
    } else {
        Err(format_err!("Invalid interface id {}", iface_id))
    }
}

async fn get_iface_ids(wlan_svc: WlanSvc, iface_id: Option<u16>) -> Result<Vec<u16>, Error> {
    match iface_id {
        Some(id) => Ok(vec![id]),
        None => {
            let response = wlan_svc.list_ifaces() await?
                .context("error listing ifaces")?;
            Ok(response.ifaces.into_iter().map(|iface| iface.iface_id).collect())
        }
    }
}

async fn list_minstrel_peers(wlan_svc: WlanSvc, iface_id: u16) -> Result<Vec<MacAddr>, Error> {
    let (status, resp) = wlan_svc.get_minstrel_list(iface_id) await?
        .context(format!("Error getting minstrel peer list iface {}", iface_id))?;
    if status == zx::sys::ZX_OK {
        Ok(resp
            .peers
            .into_iter()
            .map(|v| {
                let mut arr = [0u8; 6];
                arr.copy_from_slice(v.as_slice());
                MacAddr(arr)
            })
            .collect())
    } else {
        println!("Error getting minstrel peer list from iface {}: {}", iface_id, status);
        Ok(vec![])
    }
}

async fn show_minstrel_peer_for_iface(
    wlan_svc: WlanSvc,
    id: u16,
    peer_addr: Option<MacAddr>,
) -> Result<(), Error> {
    let peer_addrs = get_peer_addrs(wlan_svc.clone(), id, peer_addr) await??;
    let mut first_peer = true;
    for mut peer_addr in peer_addrs {
        let (status, resp) = wlan_svc.get_minstrel_stats(id, &mut peer_addr.0) await?
            .context(format!("Error getting minstrel stats from peer {}", peer_addr))?;
        if status != zx::sys::ZX_OK {
            println!(
                "error getting minstrel stats for {} from iface {}: {}",
                peer_addr, id, status
            );
        } else if let Some(peer) = resp {
            if first_peer {
                println!("iface {}", id);
                first_peer = false;
            }
            print_minstrel_stats(peer);
        }
    }
    Ok(())
}

async fn get_peer_addrs(
    wlan_svc: WlanSvc,
    iface_id: u16,
    peer_addr: Option<MacAddr>,
) -> Result<Vec<MacAddr>, Error> {
    match peer_addr {
        Some(addr) => Ok(vec![addr]),
        None => list_minstrel_peers(wlan_svc, iface_id) await?,
    }
}

fn print_minstrel_stats(mut peer: Box<Peer>) {
    let total_attempts: f64 = peer.entries.iter().map(|e| e.attempts_total as f64).sum();
    let total_success: f64 = peer.entries.iter().map(|e| e.success_total as f64).sum();
    println!(
        "{}, max_tp: {}, max_probability: {}, attempts/success: {:.6}, probes: {}",
        MacAddr(peer.mac_addr),
        peer.max_tp,
        peer.max_probability,
        total_attempts / total_success,
        peer.probes
    );
    println!(
        "     TxVector                            succ_c   att_c  succ_t   att_t \
         probability throughput probes probe_cycles_skipped"
    );
    peer.entries.sort_by(|l, r| l.tx_vector_idx.cmp(&r.tx_vector_idx));
    for e in peer.entries {
        println!(
            "{}{} {:<36} {:7} {:7} {:7} {:7} {:11.4} {:10.3} {:6} {:20}",
            if e.tx_vector_idx == peer.max_tp { "T" } else { " " },
            if e.tx_vector_idx == peer.max_probability { "P" } else { " " },
            e.tx_vec_desc,
            e.success_cur,
            e.attempts_cur,
            e.success_total,
            e.attempts_total,
            e.probability * 100.0,
            e.cur_tp,
            e.probes_total,
            e.probe_cycles_skipped,
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn format_bssid() {
        assert_eq!(
            "01:02:03:ab:cd:ef",
            format!("{}", MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef]))
        );
    }

    #[test]
    fn mac_addr_from_str() {
        assert_eq!(
            MacAddr::from_str("01:02:03:ab:cd:ef").unwrap(),
            MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef])
        );
        assert_eq!(
            MacAddr::from_str("01:02-03:ab-cd:ef").unwrap(),
            MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef])
        );
        assert!(MacAddr::from_str("01:02:03:ab:cd").is_err());
        assert!(MacAddr::from_str("01:02:03:04:05:06:07").is_err());
        assert!(MacAddr::from_str("01:02:gg:gg:gg:gg").is_err());
    }
}

Feedback needed

It could be too late to post yet another proposal, but at least this syntax wasn't discussed previously, it wasn't listed as a viable alternative in any summary, and it wasn't subject of any straw poll.

I hope not only me excited about it and that it still has a little chance of being appreciated before the final decision made by the Rust language team isn't posted yet.

1 Like

This has already been considered, and detractors don't like it because it visually groups things incorrectly when chaining.

Also the team has already made a decision on the 23rd (as they said they would in the first post) and we need to wait for their formal response to the community.

edit: link to update

15 Likes

Edit: I misunderstood; the proposal here isn’t quite “space syntax” because the proposed operator is await? including the question mark.


This is “space syntax” in the original paper summary.

I’d also pondered this one formatted without the space (as would be allowed by the tokenizing rules) when used after a function call (as it almost always is, because delaying is less useful in the nothing-until-polled model), as a potential solution to the grouping issue that @RustyYato mentioned:

let response = wlan_svc.list_ifaces()await
    .context("error getting response")?;
2 Likes

Could you elaborate? I've tried to find it on the following threads loading them fully and using Ctrl-f:

Moreover, I've tried it now again to ensure that I've not missed something, but found nothing relevant.

It's not "space syntax", and it's either missing in the summary you linked.


Probably you both misunderstood my proposal a bit

I also thought this was just space syntax (with “await” spelled as “await?”), but I was having a hard time telling for sure if that was specifically what you’re proposing. In what way is it not space syntax?

3 Likes

In your subjective opinon.

No, it doesn't. Rust doesn't use keywords after expression (except as).

There is no single opinion or guideline about how "all keywords should look".

Your example looks like .context should apply to await?, not to wlan_svc.list_phys() await?.

Simply because space has lower precedence than a dot. E. g.

foo.bar as baz
// is
(foo.bar) as baz
// but not
foo.(bar as baz)

"Nobody" is a bit too bold word.

Fingers crossed and hoping for the best.

5 Likes

There are very important differences between "space await" and "asking await"

  1. "space syntax" don't attach await to the expression on LHS, while in "asking await" there's a question mark which groups everything on LHS into a single "question"
  2. "space syntax" doesn't emphasize that await is a fallible operation, while "asking await" explicitly says that this operation could be interrupted
  3. "space syntax" is promoted as postfix keyword, while "asking await" is nothing but an extension of a ? operator like &mut is extension of &, e.g. let x = &mut future await?;

That seems worse, because it looks like you are using the try operator (?) but you’re not. This is confusing. But either way, it is too late now for a proposal, as the decision has already been made and we are just waiting on the response.

6 Likes

Actually, you will use ? operator. What's weird with that? How it's confusing?

Oh, I think I see. So the equivalent of let x = foo().await?; in the team's proposal would be let x = foo() await??; in "asking await"?

If so, I find the adjacent question marks with different meaning very surprising. (At least in &&mut x they're bother references, but the closest I can come up with for await?? is "they're both monads".)

4 Likes

Right, it would be. Also, you may be interested in this example.

Why do you think they have a different meaning? They have the same meaning: ensure that the preceding expression was executed successfully.

And actually they both are kind of monadic operations

@160R Keep in mind that std Futures are not fallible.

Future<Item = T> exists, and is awaited without an error return. (Note that the drop case when Future::poll is not called again is different from a return.)

Await isn’t fallible. It’s cancellable.

The case when await leads to dropping the Future context instead of continuing is completely unrelated to the ? error case return, and it would be a bad idea to conflate the two. You have “control” over ? (in that it’s your value that you apply ? to) and the environment has control over the await.

7 Likes

Why it would be a bad idea? Their difference is completely irrelevant when we read or write code.

Consider the following example:

    producer      ?.consumer();
    producer await?.consumer()

here, from .consumer() perspective it absolutely doesn't matter if producer failed to execute because it was cancelled or because it returned a wrong value.

It does matter, because if it is cancelled, then no value is returned from the function. Also, if I see a ? then I assume I am dealing with a Try type, and that there is an early return (it seems bad to overload such a common sigil). It would quickly get confusing.

Consider new users, if they see ? they will likely assume that something fallible is being handled. Not that it is part of the await syntax.

2 Likes

Could you explain further? I don't understand why users should think about this at all

How hard it would be for you to become comfortable with it?

New users will likely don't know that ? is used for error handling and we would teach them differently

Yes, and this is another special case in the language. I dislike adding special cases whenever possible.

If you have some invariants in your code that depend on whether you function returns, especially with unsafe code

It's not a special case. We would teach users that ? is a general postfix "ensure" operator. In the same way as & is a general borrowing operator. No special case would be added into language.

Moreover, special cases would be added if we would select different postfix syntax (field/method/macro/keyword/etc.).

  • It would be the selected syntax for await
  • And there would remain another special case in the form of ? operator

So, actually this proposal removes a special case!

Could you provide an example of such code, and estimate how often it would occur?

I'm pretty sure that we wouldn't care about it in most of the times

I am not going to continue responding to this discussion because I don’t think that it is useful to continue after the deadline of May 23rd. I laid out why I don’t like this, and it seems like we just have different values and ways of seeing this, which is fine. But this discussion will not change anything because we are past the deadline.

8 Likes

If you’re interested in reducing the burden of cancellation correctness without changing await syntax, see Can we reduce the burden of cancel-correctness for async Futures?.

1 Like

What we know about the language team decision:

  1. The final syntax is only MVP and it was explicitly said that future extensions are possible
  2. The final syntax was selected from the set of options which excludes current option
  3. The final syntax isn’t stabilized yet

Therefore, I don’t see any certain reason why this discussion will not be constructive, neither I don’t see what’s wrong if we will just compare .await and await? syntaxes here.