Meaning of error E0507 "cannot move out of borrowed context" (undocumented in compiler error index)


#1

I have the following code:

impl Schema {
   pub fn validate_query(&self, query: &Query) -> Result<(), Error> {
    match (*self, *query) {
      ...
    }
  }
}

Where both the Schema and Query types are enums. When trying to compile I get the following errors:

src/value.rs:105:12: 105:17 error: cannot move out of borrowed content [E0507]
src/value.rs:105     match (*self, *query) {
                            ^~~~~
src/value.rs:105:19: 105:25 error: cannot move out of borrowed content [E0507]
src/value.rs:105     match (*self, *query) {
                                   ^~~~~~
error: aborting due to 2 previous errors

I looked at the Rust Compiler Error Index for more information about error E0507, but nothing was documented. What am I doing wrong?


#2

Indeed the issue cannot be found, even with rustc --explain.

Could you paste what is inside the match(if you can’t show the actual code, may be minimized)?
I don’t think the issue is in the match but what is inside it. It seems like valid code.


#3

Most likely you have a pattern which binds a variable to some part of self and/or query. Variables bound in patterns take ownership (“move out”) by default, but you don’t have ownership of self or query because they were passed in as references (“borrowed content”). Add the ref keyword before the binding variables to avoid this, e.g. if you have:

    (A, B(x)) => …

change it to:

    (A, B(ref x)) => ...

However, this causes x to become itself borrowed content, so you may need to change the body of the match arm to work with that.


#4

@LilianMoraru here is the full code after adding the refs like @sorear suggested. It still fails with the same error. I apologize for any other anti-patterns, I’m still new at rust so any off topic recommendations are also welcome.

impl Schema {
  pub fn validate_query(&self, query: &Query) -> Result<(), Error> {
    static NO_PRIMITIVE_HINT: &'static str = "Try not querying specific properties of a primitive like `null` or `boolean`.";
    lazy_static! { static ref INTEGER_RE: Regex = Regex::new(r"^\d+$").unwrap(); }
    let is_key_integer = |key: &Key| INTEGER_RE.is_match(&key);

    match (*self, *query) {
      (Schema::None, _) => Ok(()),
      (Schema::Null, Query::Value) => Ok(()),
      (Schema::Null, Query::Object(_)) => Err(Error::validation("Cannot deeply query null.", NO_PRIMITIVE_HINT)),
      (Schema::Boolean, Query::Value) => Ok(()),
      (Schema::Boolean, Query::Object(_)) => Err(Error::validation("Cannot deeply query a boolean.", NO_PRIMITIVE_HINT)),
      (Schema::Number{..}, Query::Value) => Ok(()),
      (Schema::Number{..}, Query::Object(_)) => Err(Error::validation("Cannot deeply query a number.", NO_PRIMITIVE_HINT)),
      (Schema::String{..}, Query::Value) => Ok(()),
      (Schema::String{..}, Query::Object(_)) => Err(Error::validation("Cannot deeply query a string.", NO_PRIMITIVE_HINT)),
      (Schema::Array{..}, Query::Value) => Ok(()),
      (Schema::Array{..}, Query::Object(ref query_properties)) => {
        match query_properties.keys().find(|k| is_key_integer(k)) {
          None => Ok(()),
          Some(bad_key) => Err(Error::validation(format!("Cannot query non-integer \"{}\" array property.", bad_key), "Only query integer array keys like 1, 2, and 3."))
        }
      },
      (Schema::Tuple{..}, Query::Value) => Ok(()),
      (Schema::Tuple{ref items, ref additional_items}, Query::Object(ref query_properties)) => {
        let is_key_in_range = |key: &Key| if *additional_items { true } else { key.parse::<usize>().map(|k| k < items.len()).unwrap_or(false) };
        match query_properties.keys().find(|k| is_key_integer(k) && is_key_in_range(k)) {
          None => Ok(()),
          Some(bad_key) => {
            if !is_key_integer(bad_key) {
              Err(Error::validation(format!("Cannot query non-integer \"{}\" array property.", bad_key), "Only query integer array keys like 1, 2, and 3."))
            } else {
              Err(Error::validation(format!("Tuple only has {} values. Can’t query the {} index.", items.len(), bad_key), format!("Query a key less than or equal to {}.", items.len() - 1)))
            }
          }
        }
      },
      (Schema::Object{..}, Query::Value) => Ok(()),
      (Schema::Object{ref properties, ref additional_properties,..}, Query::Object(ref query_properties)) => {
        let property_keys = if *additional_properties { None } else { Some(properties.keys()) };
        let is_key_queryable = |key: &Key| property_keys.clone().map(|mut ks| ks.any(|k| k == key)).unwrap_or(true);
        match query_properties.keys().find(|k| is_key_queryable(k)) {
          None => Ok(()),
          Some(bad_key) => Err(Error::validation(format!("Cannot query object property \"{}\".", bad_key), "Query an object property that is defined in the schema."))
        }
      },
      (Schema::Enum(_), Query::Value) => Ok(()),
      (Schema::Enum(_), Query::Object(_)) => Err(Error::validation("Cannot deeply query an enum.", NO_PRIMITIVE_HINT))
    }
  }
}

#5

I was wrong, it’s not the match arm binding that’s making the error, it’s the temporary tuple constructor. You don’t own *self and *query so you can’t move them into the tuple — but you do own the pointers, so you can make a tuple of pointers and match on that: playpen.


#6

Just noting that E0507 is documented in the beta error index, so it will be in stable soon: https://doc.rust-lang.org/beta/error-index.html#E0507


#7

Yep, what I thought… *self and *query are begging to be moved.

For some reason I thought that I ran rustc --explain in nightly, but yep, it works in nightly.


#8

Thanks @sorear, that was it! And thanks to everyone else for all the helpful information :smile: