I'm not sure what level you're working at.
When you say "send a FIN packet" and "send a RST" packet, you talk in terms of the implementation.
If you control the implementation, then you also control what the user has buffered. Thus, you can throw away what the user has buffered, and only keep the bit that is currently "on the wire," which is the size of the remote accept window.
If you want the connection to appear to have terminated gracefully, at the TCP level, then that's what you have to do.
However, some higher-level application protocol will probably be confused. For example, if the protocol says "here comes file X" and then data starts streaming, and then data stops streaming -- should the remote end assume that it has all of X? If there is a length field in the protocol, the remote end will know it didn't receive all of it, so the "graceful" shutdown at the TCP level still ends up being a "forceful/unexpected" shutdown at the application level.
So, if the reason you want to "immediately" stop the transfer is that you changed your mind -- you don't want to send that 20 GB file over your cell phone data plan -- then the best thing to do is to remove all the data that's on the wire as well as all the data that the user has buffered, which means tearing down the connection state, which means sending a RST, and responding with RST if you hear anything more from the other end.
Now -- this is from the point of view of the implementation. When you're writing application software, you typically don't have control at that level, but instead use some particular socket API. In Berkeley sockets (most common,) compare the actions of shutdown() with the actions of close(). They may translate to particular TCP packets on the wire, but there is no guarantee -- there is only the specific defined semantics of those API calls that the application makes.