Add Argument Contracts to a Racket Function
Use racket/contract to make a Racket function reject invalid arguments with a clear contract-violation exception instead of silent wrong results.
15 Sept 2025, 20:39 UTC

Problem and useful takeaway
A Racket procedure that receives a string where it expects a number may propagate the bad value through several calculations before failing, or worse, return an incorrect result without any error. The useful takeaway is to declare a contract at the function boundary so invalid arguments raise exn:fail:contract immediately, with a message that names the offending argument and the expected contract.
Prerequisites
- Racket installed, with
racketandracoon PATH. - A source file that begins with
#lang racket. - A text editor to modify the file.
Procedure: add a contract to a function
- Open
geometry.rkt(replace with your file name) and add(require racket/contract)after the language line. - Change
definetodefine/contract. - Write a function contract with
->: list domain contracts for the arguments, then the range contract for the result. - Save the file and load it in
racket.
A flat contract such as positive? accepts a value and returns #t or #f. The -> combinator builds a function contract: every argument must satisfy its domain contract, and the result must satisfy the range contract.
#lang racket
(require racket/contract)
(define/contract (circle-area radius)
(-> positive? real?)
(* pi radius radius))
Expect these results when you interact with the loaded module:
(circle-area 3)returns a number.(circle-area -1)raisesexn:fail:contractbecause-1failspositive?.(circle-area "three")raisesexn:fail:contractbecause a string is not a real number.
Expected checks
- Run
racket geometry.rktin the directory that contains the file. Required permissions are read access to the file and execute access to the Racket binary. - Call the function with a valid argument and confirm it returns a numeric result, not an exception.
- Call the function with an invalid argument and confirm an exception is raised instead of a wrong number.
- Wrap a risky call in
with-handlersforexn:fail:contractand confirm the handler runs instead of an uncaught exception.
(with-handlers ([exn:fail:contract
(lambda (exc)
(displayln "Contract violation caught:")
(displayln (exn-message exc)))])
(circle-area -1))
Recovery options
- Catch violations at the boundary with
with-handlerswhen input comes from users or external files. - Relax the contract: replace
positive?withreal?if zero should be accepted. - Temporarily remove the contract during debugging by changing
define/contractback todefineand removing(require racket/contract)if it is no longer used. Restore the contract before sharing or committing the code.
Limitations and practical verification
- Contracts add runtime overhead on every crossed boundary. Avoid wrapping functions inside tight numeric loops unless the validation cost is acceptable.
- Internal recursive calls can bypass the wrapper. If a contracted function calls itself by its internal name, recursive calls skip the contract. Call through the public binding or contract the recursive helper separately.
- Contract messages can expose argument values. Do not apply contracts that log or display secrets in production contexts.
- Exact exception wording may vary by Racket release. Test for the
exn:fail:contractexception class rather than a specific message string.
Practical verification: load the module, call (circle-area 1) and confirm it returns pi. Then call (circle-area -1) and confirm the exception class is exn:fail:contract. If no exception appears, the contract wrapper was not applied or the call bypassed it.
Rollback
Adding a contract changes runtime behavior. To roll back, edit the source file, replace define/contract with define, remove (require racket/contract) if no other contract remains, save, and reload the module. The function then accepts any argument without validation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.