In the previous sections, all of the examples have consisted of programs that are entirely typed. One of the key features of Typed Racket is that it allows the combination of both typed and untyped code in a single program.
Suppose that we write the untyped module from Quick Start again:
If we want to use the distance function defined in the above module from a typed module, we need to use the require/typed form to import it. Since the untyped module did not specify any types, we need to annotate the imports with types (just like how the example in Quick Start had additional type annotations with :):
#lang typed/racket (require/typed "distance.rkt" [#:struct pt ([x : Real] [y : Real])] [distance (pt pt -> Real)]) (distance (pt 3 5) (p 7 0))
Note that the require/typed form can import bindings from any module, including those that are part of the Racket standard library. For example,
#lang typed/racket (require/typed racket/base [add1 (Integer -> Integer)])
In the previous subsection, we saw that the use of untyped code from typed code requires the use of require/typed. However, the use of code in the other direction (i.e., the use of typed code from untyped code) requires no additional work.
If an untyped module requires a typed module, it will be able to use the bindings defined in the typed module as expected. The major exception to this rule is that macros defined in typed modules may not be used in untyped modules.
One might wonder if the interactions described in the first two subsections are actually safe; after all, untyped code might be able to ignore the errors that Typed Racket’s type system will catch at compile-time.
To ensure that typed-untyped interactions are safe, Typed Racket establishes contracts wherever typed and untyped code interact. For example, suppose that we write an untyped module that implements an increment function:
For general information on Racket’s contract system , see Contracts.
> (module increment racket (provide increment) ; increment : exact-integer? -> exact-integer? (define (increment x) "this is broken"))
and a typed module that uses it:
> (module client typed/racket (require/typed 'increment [increment (Integer -> Integer)]) (increment 5))
This combined program is not correct. All uses of increment in Typed Racket are correct under the assumption that the increment function upholds the (Integer -> Integer) type. Unfortunately, our increment implementation does not actually uphold this assumption, because the function actually produces strings.
On the other hand, when the program is run:
> (require 'client)
increment: broke its contract
produced: "this is broken"
in: the range of
(-> Integer Integer)
contract from: (interface for increment)
blaming: (interface for increment)
we find that the contract system checks the assumption made by the typed module and correctly finds that the assumption failed because of the implementation in the untyped module (hence it is blamed in the error message).
In the same fashion, Typed Racket checks all functions and other values that pass from a typed module to untyped module or vice versa with contracts. This means that, for example, Typed Racket can safely optimize programs (see Optimization in Typed Racket) with the assurance that the program will not segfault due to an unchecked assumption.
Important caveat: contracts such as the Integer check from above are performant. However, contracts in general can have a non-trivial performance impact, especially with the use of first-class functions or other higher-order data such as vectors.
Note that no contract overhead is ever incurred for uses of typed values from another typed module.