Operators

Fastly VCL provides various arithmetic and conditional operators.

Operator precedence

Operator precedence defines the order of operations when evaluating an expression. Higher precedence operators are evaluated before those with lower precedence. Operators are listed in the following table, highest precedence first. For example, a || b && c reads as a || (b && c) because && has higher precedence than ||.

Operator associativity determines which side binds first for multiple instances of the same operator at equal precedence. For example, a && b && c reads as (a && b) && c because && has left to right associativity.

OperatorNameAssociativity
( )Grouping for precedenceleft to right
!Boolean NOTright to left
&&Boolean ANDleft to right
||Boolean ORleft to right

HINT: use explicit parentheses in expressions to avoid having to remember operator precedence rules.

Negation

- is a unary operator, which can be prefixed to numeric literals (numbers) to make them negative, e.g. -4, -87.3.

String concatenation

Adjacent strings are concatenated implicitly, or explicitly by using the + operator. Both of the following lines of code achieve the same result:

set req.http.some-header = "helloworld";
set req.http.some-header = "hello" "world";
set req.http.some-header = "hello" + "world";

Conditional operators

Conditional operators produce BOOL values, suitable for use in if (...) statements:

if (!(beresp.status >= 500 && beresp.status < 600)) {
# ... the response is not an HTTP 5XX status
}

Conditional expressions may be inverted by prefixing the ! operator, and combined using AND and OR operators (&& and || respectively).

WARNING: Logical operators have short-circuit evaluation, whereby the right-hand side is only evaluated when necessary. For example, given a && b, if a is false the resulting value will always be false, so there's no need to evaluate b. This can be important when the right-hand operand has a visible side effect, such as a call to a function.

The following operators are valid for comparisons:

OperatorPurposeExampleTypes
==Equalresp.status == 200 (note there is no === in VCL)All types
!=Not equalreq.url.path != "/"All types
~Matches regexreq.url ~ "^/products(/?.*)$"STRING
~Matches ACLclient.ip ~ allowed_usersIP
!~Does not match regexreq.http.cookie:optin !~ "ads"STRING
!~Does not match ACLclient.ip !~ blocked_usersIP
>Greater thanreq.restarts > 0Numeric types and TIME
<Less thannow < var.expiry_timeNumeric types and TIME
>=At leastresp.status >= 500Numeric types and TIME
<=At mostrandomint(1,100) <= 75Numeric types and TIME

FLOAT comparisons have special cases for operands which are NaN: The != operator always evaluates to true when either operand is NaN. All other conditional operators always evaluate to false when either operand is NaN.

For example, if a given variable is NaN, that variable will compare unequal to itself: both var.nan == var.nan and var.nan >= var.nan will be false.

STRING comparisons have special cases for operands which are not set (as opposed to empty): The != and !~ operators always evaluate to true when either operand is not set. All other conditional operators always evaluate to false when either operand is not set. For example, if a variable is not set, that variable will compare unequal to itself: Both req.http.unset == req.http.unset and req.http.unset ~ ".?" will be false.

Floating point infinities are signed, and compare as beyond the maximum and minimum values for FLOAT types, such that for any finite value: −∞ < n < +∞.

Note that as there are currently no numeric expressions in general; these operators are limited to use with specific operands. For example, var.i < 5 is permitted but 2 < 5 is not.

Assignment operators

These operators allow values or the results of expressions to be assigned to a variable using the set statement.

OperatorPurposeExample
=Simple assignmentset var.my_string = "42";
+=Additionset var.my_integer += 4;
-=Subtractionset var.my_integer -= 4;
*=Multiplicationset var.my_integer *= 4;
/=Divisionset var.my_integer /= 4;
%=Modulusset var.my_integer %= 4;
|=Bitwise ORset var.my_integer |= 16;
&=Bitwise ANDset var.my_integer &= 65280;
^=Bitwise XORset var.my_integer ^= 16;
<<=Left shiftset var.my_integer <<= 4;
>>=Right shiftset var.my_integer >>= 4;
rol=Left rotateset var.my_integer rol= 4;
ror=Right rotateset var.my_integer ror= 4;
&&=Logical ANDset var.my_boolean &&= var.is_authenticated;
||=Logical ORset var.my_boolean ||= randombool(1,3);

If both operands (left-hand side and right-hand-side) are INTEGER, an INTEGER variant of the operator, where available, is used. The advantage of this is that the operation is faster and more precise (compared with FLOAT variants), but because the range of INTEGER is more limited, overflows can happen. For example, the integer variants of the +=, -=, and *= operators wrap around as if they were unsigned 64-bit integers.

FLOAT arithmetic has special cases for operands which are NaN: Arithmetic operators evaluate to NaN when either operand is NaN.

FLOAT arithmetic has special cases for operands which are floating point infinities: In general all arithmetic operations evaluate to positive or negative infinity when either operand is infinity. However, some situations evaluate to NaN. Some of these situations are domain errors, in which case fastly.error is set to EDOM accordingly. Others situations are not domain errors: ∞ − ∞ and 0 × ∞. These evaluate to NaN but do not set fastly.error.

The left-shift <<= is equivalent to multiplying by powers of two, but the high-order bits disappearing. For example, <<= 3 is equivalent to multiplying by 8, but the highest three bits will disappear.

The right-shift >>= is the arithmetic shift: that is, for both positive and negative integers it is equivalent to dividing by powers of two. For example, >>= 3 is equivalent to dividing by 8, but the lowest three bits will disappear. For negative integers this is also known as sign-extension: at the binary level, the sign bit (the highest bit) is copied to all the vacant bits.

The left-rotate rol= moves the bits towards the high end but the bits dropping off from the high end are rotated back to the low end. For example, rol= 1 moves the highest bit to the low end, and moves all the other bits up.

The right-rotate ror= moves the bits towards the low end but the bits dropping off the low end are rotated back to the high end. For example, ror= 1 moves the lowest bit to the high end, and moves all the other bits down.

Shift and rotate operations with negative amounts perform the operation in the opposite direction. For example, <<= -3 is equivalent to >>= 3, and rol= >> -3 is equivalent to ror= 3.

For right operands larger than the bit width of INTEGER (64), shifts will yield zero for non-negative numbers, or -1 for negative numbers, and rotates will use the operand modulo the bit width of INTEGER. For example, >>= 99 will yield zero or -1, and ror= 99 is equivalent to ror= 35 because 99 % 64 == 35.

Reserved punctuation

Punctuation appears in various syntactic roles which are not operators (that is, they do not produce a value).

PunctuationExample Uses
{ }Block syntax
[ ]Stats ranges
( )Syntax around if conditions, function argument lists
/Netmasks for ACLs
,Separator for function arguments
;Separator for statements and various other syntactic things
!Invert ACL entry
.To prefix fields in backend declarations
:Port numbers for backend declarations, and used in the stats syntax

The following lexical tokens are reserved, but not used: * & | >> << ++ -- %