On this page:
7.9.1 Contracts and eq?
7.9.2 Contract boundaries and define/  contract
7.9.3 Exists Contracts and Predicates
7.9.4 Defining Recursive Contracts
7.9.5 Mixing set! and contract-out

7.9 Gotchas🔗

7.9.1 Contracts and eq?🔗

As a general rule, adding a contract to a program should either leave the behavior of the program unchanged, or should signal a contract violation. And this is almost true for Racket contracts, with one exception: eq?.

The eq? procedure is designed to be fast and does not provide much in the way of guarantees, except that if it returns true, it means that the two values behave identically in all respects. Internally, this is implemented as pointer equality at a low-level so it exposes information about how Racket is implemented (and how contracts are implemented).

Contracts interact poorly with eq? because function contract checking is implemented internally as wrapper functions. For example, consider this module:
#lang racket
 
(define (make-adder x)
  (if (= 1 x)
      add1
      (lambda (y) (+ x y))))
(provide (contract-out
          [make-adder (-> number? (-> number? number?))]))

It exports the make-adder function that is the usual curried addition function, except that it returns Racket’s add1 when its input is 1.

You might expect that
(eq? (make-adder 1)
     (make-adder 1))

would return #t, but it does not. If the contract were changed to any/c (or even (-> number? any/c)), then the eq? call would return #t.

Moral: Do not use eq? on values that have contracts.

7.9.2 Contract boundaries and define/contract🔗

The contract boundaries established by define/contract, which creates a nested contract boundary, are sometimes unintuitive. This is especially true when multiple functions or other values with contracts interact. For example, consider these two interacting functions:

> (define/contract (f x)
    (-> integer? integer?)
    x)
> (define/contract (g)
    (-> string?)
    (f "not an integer"))
> (g)

f: contract violation

  expected: integer?

  given: "not an integer"

  in: the 1st argument of

      (-> integer? integer?)

  contract from: (function f)

  blaming: top-level

   (assuming the contract is correct)

  at: eval:2:0

One might expect that the function g will be blamed for breaking the terms of its contract with f. Blaming g would be right if f and g were directly establishing contracts with each other. They aren’t, however. Instead, the access between f and g is mediated through the top-level of the enclosing module.

More precisely, f and the top-level of the module have the (-> integer? integer?) contract mediating their interaction; g and the top-level have (-> string?) mediating their interaction, but there is no contract directly between f and g. This means that the reference to f in the body of g is really the top-level of the module’s responsibility, not g’s. In other words, the function f has been given to g with no contract between g and the top-level and thus the top-level is blamed.

If we wanted to add a contract between g and the top-level, we can use define/contract’s #:freevar declaration and see the expected blame:

> (define/contract (f x)
    (-> integer? integer?)
    x)
> (define/contract (g)
    (-> string?)
    #:freevar f (-> integer? integer?)
    (f "not an integer"))
> (g)

f: contract violation

  expected: integer?

  given: "not an integer"

  in: the 1st argument of

      (-> integer? integer?)

  contract from: top-level

  blaming: (function g)

   (assuming the contract is correct)

  at: eval:6:0

Moral: if two values with contracts should interact, put them in separate modules with contracts at the module boundary or use #:freevar.

7.9.3 Exists Contracts and Predicates🔗

Much like the eq? example above, #:∃ contracts can change the behavior of a program.

Specifically, the null? predicate (and many other predicates) return #f for #:∃ contracts, and changing one of those contracts to any/c means that null? might now return #t instead, resulting in arbitrarily different behavior depending on how this boolean might flow around in the program.

Moral: Do not use predicates on #:∃ contracts.

7.9.4 Defining Recursive Contracts🔗

When defining a self-referential contract, it is natural to use define. For example, one might try to write a contract on streams like this:

> (define stream/c
    (promise/c
     (or/c null?
           (cons/c number? stream/c))))

stream/c: undefined;

 cannot reference an identifier before its definition

  in module: top-level

Unfortunately, this does not work because the value of stream/c is needed before it is defined. Put another way, all of the combinators evaluate their arguments eagerly, even though the values that they accept do not.

Instead, use
(define stream/c
  (promise/c
   (or/c
    null?
    (cons/c number? (recursive-contract stream/c)))))

The use of recursive-contract delays the evaluation of the identifier stream/c until after the contract is first checked, long enough to ensure that stream/c is defined.

See also Checking Properties of Data Structures.

7.9.5 Mixing set! and contract-out🔗

The contract library assumes that variables exported via contract-out are not assigned to, but does not enforce it. Accordingly, if you try to set! those variables, you may be surprised. Consider the following example:

> (module server racket
    (define (inc-x!) (set! x (+ x 1)))
    (define x 0)
    (provide (contract-out [inc-x! (-> void?)]
                           [x integer?])))
> (module client racket
    (require 'server)
  
    (define (print-latest) (printf "x is ~s\n" x))
  
    (print-latest)
    (inc-x!)
    (print-latest))
> (require 'client)

x is 0

x is 0

Both calls to print-latest print 0, even though the value of x has been incremented (and the change is visible inside the module x).

To work around this, export accessor functions, rather than exporting the variable directly, like this:

#lang racket
 
(define (get-x) x)
(define (inc-x!) (set! x (+ x 1)))
(define x 0)
(provide (contract-out [inc-x! (-> void?)]
                       [get-x (-> integer?)]))

Moral: This is a bug that we will address in a future release.