cut_width knows nothing of the physical laws of the universe, so does not know that carat should be positive. Let's see why it's doing that. I'm currently on ggplot2-3.2.1, so some lines might have been updated in newer versions.
debugonce(cut_width)
cut_width(diamonds$carat, 0.5)
# debug: {
# x <- as.numeric(x)
# width <- as.numeric(width)
# ...truncated...
Step down until most helper variables are defined, then
x_range
# [1] 0.20 5.01
boundary
# [1] 0.25
c(min_x, max_x)
# [1] -0.25 5.51
breaks
# [1] -0.25 0.25 0.75 1.25 1.75 2.25 2.75 3.25 3.75 4.25 4.75 5.25
Important is that we know the data ranges from 0.2 to 5.01 (x_range), boundary is half-width (per the code), and min_x is determined by another helper-function, find_origin. Why does this function think that -0.25 is a reasonable first-bin start? The code is not very clear about this (I'd ask the authors).
If you want to control it, add boundary=:
levels(cut_width(diamonds$carat, 0.5))
# [1] "[-0.25,0.25]" "(0.25,0.75]" "(0.75,1.25]" "(1.25,1.75]" "(1.75,2.25]" "(2.25,2.75]" "(2.75,3.25]" "(3.25,3.75]"
# [9] "(3.75,4.25]" "(4.25,4.75]" "(4.75,5.25]"
levels(cut_width(diamonds$carat, 0.5, boundary=0))
# [1] "[0,0.5]" "(0.5,1]" "(1,1.5]" "(1.5,2]" "(2,2.5]" "(2.5,3]" "(3,3.5]" "(3.5,4]" "(4,4.5]" "(4.5,5]" "(5,5.5]"