packageOverrides are in fact currently implemented as a built-in overlay. The overlay used for
packageOverrides sits directly beneath the ones implemented as custom overlays.
As stated before,
packageOverrides don’t compose well. The main problem is the only argument it has is the
super set, i.e. the set of packages immediately prior to
packageOverrides (and later overlays) being applied. This means that any packages subsequently modified by other overrides are not accessible to
packageOverrides even though they are accessible to other overlays via the
callPackage itself will still use the
self package set for resolving dependencies, so your
z.nix there will have any dependencies provided by overlays if applicable, but any direct references to packages from the
pkgs set won’t take overlays into account.
Overlays are also just easier to work with. Drop a new file or dir into
<nixpkgs-overlays> if defined in
NIX_PATH) and you have yourself an overlay. You can easily add and remove overlays without editing any individual file. You can distribute overlays as git repos that get checked out into this directory, or have install scripts that symlink overlays into this directory (such as rust-overlay-install.sh). You can import
nixpkgs with a custom set of overlays (I do this in a maintenance script for my custom overlays, it takes each existing overlay and wraps it in a function that records all of the overlay packages, in order to search just those packages for update scripts).