Service locators allow you to remove required data from an interface of an object and instead get these data from a secondary object - the service locator.

We normally have direct dependency declarations in APIs:

    class A(data1: B, data2: C)
    {
    }

Compare this to using a service locator:

    class A(locator: L)
    {
        data1 = locator.locate_data1();
        data2 = locator.locate_data2();
    }
Where code internal to the A acquires B and C from the locator.

Drawbacks

The biggest drawback with a service locator is that we now make dependencies implicit. Depending on whether your locator default-constructs locatables on non-existence or not, your program can also start failing dynamically instead of statically.

Another drawback is the potential creation of cycles. In a language without a garbage collector this can lead to memory leaks.

Advantages

This pattern is useful if we need provide dependencies to a heterogeneous set of classes all with their own peculiar dependencies, while these classes are all created in a uniform way. An example is providing input data to a set of heterogeneous types.
    let v: Collection<dyn SomeInterface> = ...;

    let locator = ...;

    for a in v {
        v.use_locator(locator):
    }

Solving cycles

One can solve cycles by only using weak pointers. That introduces the problem of services being removed from the locator and hence the weak pointer not resolving. That again can be solved by not allowing services to be removed.

Mutable Aliasing

MutableAliasing is another problem, if service A depends on B and C, and both depend on D, then A can mutably alias D. This can be prevented by performing a graph check during initialization.