I don’t think this is correct. As I struggled a lot with this post and couldn’t believe that there is a mistake here, I want to share what I found:
I checked the Marlin code, and in fact get_position(), get_polar(), set_position() and set_polar() use the information of the stepper motors to calculate the position and the (cylindrical) polar coordinates, respectively. You have to call get_servo_angle() to get the encoder information. However, this returns the encoder readings as angles of the three encoders, rotation at the base, and the two angles of the arm. The conversion of these angles into x/y/z coordinates is a little bit complicated and done in Marlin internally. The function swift.angles_to_coordinates(angles) allows you to let Marlin convert them for you.
To verify this, you can run a script as the following and compare the outputs of:
swift.set_position(x, y, z)
print(x, y, z)
You will see that - as you said - it is true that x, y, z and the return value of get_position differ slightly. However, this is only due to rounding errors in the interpolation Marlin does in the background. Those values will not differ any more or less if you e.g. force the arm to skip a step by blocking its movement. The return value of the last line will actually read the encoder values and convert it to x/y/z coordinates, and this will also reflect any missed steps. As set_position and set_polar check the encoders before moving, they will still always aim at the right target, even if they missed steps before.
You can also call set_servo_detach(), which will turn the motors off, allowing you to move the arm freely. When you call get_position() or get_polar() now, they will return the values of the last position when the servos were still attached. This is again due to them calculating the position from the number of steps Marlin thinks the motors did. However, get_servo_angle() will still give you the correct position of the arm.