In Operator

Trivia

The in operator is simply syntactic sugar for a call to the contains function.

Similarly, !in is a call to !contains.

The in operator is used to check for containment – i.e. whether a particular collection data type contains a particular item.

Similarly, !in is used to check for non-existence – i.e. it is true if a particular collection data type does not contain a particular item.

42 in array;

array.contains(42);     // <- the above is equivalent to this

123 !in array;

!array.contains(123);   // <- the above is equivalent to this

Built-in Support for Standard Data Types

Data typeCheck for
Numeric rangeinteger number
Arraycontained item
Object mapproperty name
Stringsub-string or character

Examples

let array = [1, "abc", 42, ()];

42 in array == true;                // check array for item

let map = #{
    foo: 42,
    bar: true,
    baz: "hello"
};

"foo" in map == true;               // check object map for property name

'w' in "hello, world!" == true;     // check string for character

'w' !in "hello, world!" == false;

"wor" in "hello, world" == true;    // check string for sub-string

42 in -100..100 == true;            // check range for number

Array Items Comparison

The default implementation of the in operator for arrays uses the == operator (if defined) to compare items.

== defaults to false

For a custom type, == defaults to false when comparing it with a value of of the same type.

See the section on Logic Operators for more details.

let ts = new_ts();                  // assume 'new_ts' returns a custom type

let array = [1, 2, 3, ts, 42, 999];
//                    ^^ custom type

42 in array == true;                // 42 cannot be compared with 'ts'
                                    // so it defaults to 'false'
                                    // because == operator is not defined

Custom Implementation of contains

The in and !in operators map directly to a call to a function contains with the two operands switched.

// This expression...
item in container

// maps to this...
contains(container, item)

// or...
container.contains(item)

Support for the in and !in operators can be easily extended to other types by registering a custom binary function named contains with the correct parameter types.

Since !in maps to !(... in ...), contains is enough to support both operators.

let mut engine = Engine::new();

engine.register_type::<TestStruct>()
      .register_fn("new_ts", || TestStruct::new())
      .register_fn("contains", |ts: &mut TestStruct, item: i64| -> bool {
          // Remember the parameters are switched from the 'in' expression
          ts.contains(item)
      });

// Now the 'in' operator can be used for 'TestStruct' and integer

engine.run(
r#"
    let ts = new_ts();

    if 42 in ts {                   // this calls 'ts.contains(42)'
        print("I got 42!");
    } else if 123 !in ts {          // this calls '!ts.contains(123)'
        print("I ain't got 123!");
    }

    let err = "hello" in ts;        // <- runtime error: 'contains' not found
                                    //    for 'TestStruct' and string
"#)?;