Doctesting private functions


#1

Hi all,

I have a small simple function that would be perfect for some doctests. However, it turns out that you cannot write doctests for private functions…

That kind of makes sense when I think about it: doctests are compiled as small external programs that call your code:

use your_crate::foo;

assert_true!(foo(123), 234);

However, perhaps we could make this easier? Something like compiling doctests as if they were a sub-module of the current module (like the typical tests module is). That would allow you to call private functions in your doctests.

Further, could we not automatically use super::*; in this doctest module? Again, this would be similar to how the documentation suggests one uses the tests module. It would allow very succint doctests:

/// My great function
///
/// # Examples
///
/// ```
/// assert_eq!(foo(123), 234);
/// assert_eq!(foo(100), 200);
/// ```
fn foo(x: i32) -> i32 { ... }

Does this sound possible?


#2

I personally feel that this is a feature. It allows to see directly from an example what needs to be imported in order to do the thing from the example.


#3

Yeah, there’s some merit in that. If the examples were written somewhere “away” from the functions and methods, then I would agree that it’s a great feature.

However, the whole point of doctests is that they’re are right there next to the function or method they describe. They should have nearly zero overhead, be small and lightweight. So it felt natural to me that they should work as if they were part of the module where the code lives.

I come from Python, and I believe Rust doctests are heavily inspired by Python doctests. The examples given there show nicely how little overhead one can have in a doctest:

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000

    Negative values are an error:

    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0
    """

    import math
    # ...

There are no imports, and thanks to the interpreter, there are also no assert_eq! statements and even no code block fences since >>> tells you where the statements start.

So thinking some more about this, I guess I have two points :slight_smile:

  1. Doctests for private functions and modules. That is, let me move some of my unit tests from a tests module back to the docstrings that describe the elements.

    I have no idea whatsoever if it’s even technically possible to compile a piece of code “as if” it was in a sub-module of some other code.

  2. Potentially making doctests easier to write in general by putting the module where they live into scope. We already add an extern crate foo to the doctests before compiling them, so there’s some precedence for this kind of convenience.