A DI Container is a library that you can use in various ways:
- The simplest (pseudo) DI Container you can write is basically juts a glorified dictionary. You can build one in 15 lines of code. However, it doesn't do Auto-Wiring, which means that it doesn't do composition, so at this point you can only use it as a Service Locator, but since Service Locator is an anti-pattern, there's not much benefit from that.
- A better DI Container also does Auto-Wiring. As soon as it can do Auto-Wiring, you can also use it as a Composition Engine. This means that you can design your own code exclusively on container-agnostic patterns like Constructor Injection, and then in the Composition Root ask the Composition Engine (DI Container) to compose everything together.
As soon as a DI Container supports Auto-Wiring, you can use it in both ways, but you should only use it as a Composition Engine.
Since Service Locator is an anti-pattern, the glorified dictionary has no value. As a bare minimum then, I'd say that a library must support Auto-Wiring in order to be a DI Container. However, that still doesn't make it a valuable component. To derive value from a DI Container, it must support sophisticated convention-based heuristics; if it doesn't, you're better off writing the composition by hand (AKA Poor Man's DI).